"use client";

// app/admin/draw/DrawStepper.tsx
// The FORWARD-ONLY guided draw stepper (admin.md 4.2; premortem 14.4).
//
// Steps (a nervous operator on a livestream cannot skip a control):
//   1. Pre-flight checklist  -> HARD-GATES commit/reveal on:
//        (a) MININTER resolution reference attached to the event
//        (b) "representante del gobierno presente" confirmation
//        (c) a minutes-fresh reconciliation that is GREEN (class-1 / class-3 open
//            => the stepper REFUSES to proceed)
//   2. Commit  -> publishes serverSeedHash + drandRound + snapshotHash + count
//                 (shown in large type for the camera)
//   3. Reveal and draw  -> reveals the seed, fetches verified drand, selects
//                 winners (relay default; manual fallback for an unreachable round)
//   4. Verify  -> shows the re-runnable bundle + the independent re-run verdict
//
// Hard rules surfaced in the UI: a half-revealed draw is VOIDED, not patched; the
// on-camera holding script is always available; reveal is idempotent (refused twice).
//
// Accessibility (WCAG 2.1 AA launch gate): semantic step list, labeled inputs,
// text-not-color status, 44px controls (UI primitives), aria-live announcements,
// prefers-reduced-motion respected (no custom motion is introduced here).

import * as React from "react";
import { Button, Card, Badge, Input, Checkbox } from "@/components/ui";
import type { DrawStepperState } from "./_lib/state";
import type { PreflightReconResult } from "./_lib/actions";
import {
  DRAND_LATE_SCRIPT,
  PAUSE_SCRIPT,
  LIVE_INCIDENT_RULES,
} from "./_lib/holding-script";

type Phase = "preflight" | "committed" | "revealed";

interface CommitResponse {
  drawId: string;
  raffleId: string;
  serverSeedHash: string;
  drandRound: number;
  drandRoundPublishesAt: string;
  snapshotHash: string;
  snapshotTicketCount: number;
}

interface RevealedWinner {
  prizeId: string;
  prizePosition: number;
  prizeName: string;
  ticketNo: number;
  selectionIndex: number;
}

interface RevealResponse {
  drawId: string;
  raffleId: string;
  finalSeed: string;
  drandRound: number;
  drandRandomness: string;
  winners: RevealedWinner[];
}

interface VerifyCheck {
  label: string;
  ok: boolean;
  detail?: string;
}
interface VerifyResponse {
  bundle: {
    serverSeedHash: string;
    serverSeed: string | null;
    drandRound: number;
    drandRandomness: string | null;
    finalSeed: string | null;
    snapshotHash: string;
    snapshotTicketCount: number;
    orderedTicketNos: number[];
  };
  verification: {
    matches: boolean;
    finalSeed: string;
    recomputedWinnerTicketNos: number[];
    checks: VerifyCheck[];
  } | null;
}

function phaseFromState(s: DrawStepperState): Phase {
  if (!s.draw) return "preflight";
  return s.draw.status === "committed" ? "committed" : "revealed";
}

export function DrawStepper({
  initial,
  checkReconciliation,
}: {
  initial: DrawStepperState;
  /** Server action (passed from the page) that runs the reconcileNow gate. */
  checkReconciliation: (raffleId: string) => Promise<PreflightReconResult>;
}) {
  const raffle = initial.raffle;
  const [phase, setPhase] = React.useState<Phase>(phaseFromState(initial));
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState<string | null>(null);
  const [status, setStatus] = React.useState<string>("");

  // Pre-flight checklist state.
  const [mininterRef, setMininterRef] = React.useState("");
  const [govPresent, setGovPresent] = React.useState(false);
  const [reconGreen, setReconGreen] = React.useState<boolean | null>(null);
  const [reconReason, setReconReason] = React.useState<string | null>(null);
  const [reconCheckedAt, setReconCheckedAt] = React.useState<string | null>(null);

  // Commit + reveal results.
  const [commit, setCommit] = React.useState<CommitResponse | null>(
    initial.draw
      ? {
          drawId: initial.draw.id,
          raffleId: raffle?.id ?? "",
          serverSeedHash: initial.draw.serverSeedHash,
          drandRound: initial.draw.drandRound,
          drandRoundPublishesAt: initial.draw.drandRoundPublishesAt,
          snapshotHash: initial.draw.snapshotHash,
          snapshotTicketCount: initial.draw.snapshotTicketCount,
        }
      : null,
  );
  const [reveal, setReveal] = React.useState<RevealResponse | null>(
    initial.winners.length > 0
      ? {
          drawId: initial.draw?.id ?? "",
          raffleId: raffle?.id ?? "",
          finalSeed: initial.draw?.finalSeed ?? "",
          drandRound: initial.draw?.drandRound ?? 0,
          drandRandomness: initial.draw?.drandRandomness ?? "",
          winners: initial.winners.map((w) => ({
            prizeId: "",
            prizePosition: w.prizePosition,
            prizeName: w.prizeName,
            ticketNo: w.ticketNo,
            selectionIndex: w.selectionIndex,
          })),
        }
      : null,
  );
  const [verify, setVerify] = React.useState<VerifyResponse | null>(null);

  // Manual drand fallback inputs.
  const [showManual, setShowManual] = React.useState(false);
  const [manualRandomness, setManualRandomness] = React.useState("");
  const [manualSignature, setManualSignature] = React.useState("");

  if (!raffle) {
    return (
      <Card elevation="drop">
        <p className="font-body text-body-md text-ink-strong">
          No se encontró la rifa seleccionada. Vuelve al panel y elige una rifa.
        </p>
      </Card>
    );
  }

  const apiBase = `/api/admin/raffles/${raffle.id}`;

  async function readError(res: Response): Promise<string> {
    try {
      const body = await res.json();
      return body?.error?.message ?? "Ocurrió un error.";
    } catch {
      return "Ocurrió un error procesando la solicitud.";
    }
  }

  async function runReconciliation() {
    setBusy(true);
    setError(null);
    setStatus("Reconciliando contra el proveedor de pagos...");
    try {
      // Same reconcileNow gate the commit route enforces, surfaced read-first so
      // the operator sees GREEN before going on camera (premortem 14.4).
      const result = await checkReconciliation(raffle!.id);
      setReconGreen(result.green);
      if (result.green) {
        setReconReason(null);
      } else {
        setReconReason(
          result.reason ??
            `Reconciliación en rojo. Pagos confirmados sin acreditar: ${result.confirmedUnpaid}. Reembolsos sin anular: ${result.refundedNotVoided}.`,
        );
      }
      setReconCheckedAt(result.checkedAt);
      setStatus("");
    } catch {
      setReconGreen(false);
      setReconReason("No se pudo verificar la reconciliación. Reintenta.");
      setStatus("");
    } finally {
      setBusy(false);
    }
  }

  const checklistComplete =
    mininterRef.trim().length > 0 && govPresent && reconGreen !== false;

  async function doCommit() {
    if (!checklistComplete) return;
    setBusy(true);
    setError(null);
    setStatus("Comprometiendo el sorteo. Congelando el pool de boletos...");
    try {
      const res = await fetch(`${apiBase}/draw/commit`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          mininterResolutionRef: mininterRef.trim(),
          governmentRepresentativePresent: true,
        }),
      });
      if (!res.ok) {
        const msg = await readError(res);
        setError(msg);
        setStatus("");
        return;
      }
      const body: CommitResponse = await res.json();
      setCommit(body);
      setPhase("committed");
      setStatus("Sorteo comprometido. Los valores publicados ya no pueden cambiar.");
    } catch {
      setError("No se pudo comprometer el sorteo. Reintenta.");
      setStatus("");
    } finally {
      setBusy(false);
    }
  }

  async function doReveal(useManual: boolean) {
    setBusy(true);
    setError(null);
    setStatus(
      useManual
        ? "Revelando con la ronda manual de drand..."
        : "Revelando. Obteniendo la aleatoriedad verificada de drand...",
    );
    try {
      const payload = useManual
        ? {
            source: "manual",
            manualRound: commit?.drandRound,
            manualRandomness: manualRandomness.trim(),
            manualSignature: manualSignature.trim(),
          }
        : { source: "relay" };
      const res = await fetch(`${apiBase}/draw/reveal`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      });
      if (!res.ok) {
        const msg = await readError(res);
        setError(msg);
        setStatus("");
        return;
      }
      const body: RevealResponse = await res.json();
      setReveal(body);
      setPhase("revealed");
      setStatus("Sorteo revelado. Ganadores seleccionados.");
    } catch {
      setError("No se pudo revelar el sorteo. Reintenta o usa la ronda manual.");
      setStatus("");
    } finally {
      setBusy(false);
    }
  }

  async function loadVerify() {
    setBusy(true);
    setError(null);
    try {
      const res = await fetch(`${apiBase}/verify`);
      if (!res.ok) {
        setError(await readError(res));
        return;
      }
      const body: VerifyResponse = await res.json();
      setVerify(body);
    } catch {
      setError("No se pudo cargar el certificado de verificación.");
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="flex flex-col gap-md">
      {/* Live status announcement for screen readers. */}
      <p aria-live="polite" className="sr-only">
        {status}
      </p>

      <header className="flex flex-wrap items-center gap-3">
        <h1 className="font-impact text-heading-lg font-bold text-ink-strong">
          Sorteo: {raffle.name}
        </h1>
        <Badge tone={raffle.status === "closed" ? "blue" : "neutral"}>
          {raffleStatusLabel(raffle.status)}
        </Badge>
      </header>

      {error && (
        <Card elevation="tight" className="border-2 border-primary">
          <p role="alert" className="font-body text-body-md font-bold text-primary">
            {error}
          </p>
        </Card>
      )}

      {/* Step indicator (forward-only). */}
      <ol className="flex flex-wrap gap-2" aria-label="Pasos del sorteo">
        <StepPill n={1} label="Pre-vuelo" active={phase === "preflight"} done={phase !== "preflight"} />
        <StepPill n={2} label="Comprometer" active={phase === "committed"} done={phase === "revealed"} />
        <StepPill n={3} label="Revelar y sortear" active={phase === "revealed"} done={phase === "revealed" && !!reveal} />
        <StepPill n={4} label="Verificar" active={false} done={!!verify?.verification?.matches} />
      </ol>

      {/* STEP 1: Pre-flight checklist */}
      {phase === "preflight" && (
        <Card elevation="drop" as="section" aria-labelledby="preflight-h">
          <h2 id="preflight-h" className="font-display text-heading-md font-bold text-ink-strong">
            Paso 1. Lista de pre-vuelo
          </h2>
          <p className="mt-1 font-body text-body-md text-muted">
            Todo debe estar listo antes de comprometer. La rifa debe estar cerrada
            y la reconciliación en verde.
          </p>

          <div className="mt-md flex flex-col gap-md">
            <Input
              name="mininter-ref"
              label="Referencia de la resolución MININTER"
              hint="Número o código de la resolución adjunta al evento. Es obligatorio."
              value={mininterRef}
              onChange={(e) => setMininterRef(e.target.value)}
              required
            />

            <Checkbox
              name="gov-present"
              label="El representante del gobierno (veedor DGIN) está presente"
              checked={govPresent}
              onChange={(e) => setGovPresent(e.target.checked)}
            />

            <div className="rounded-md border border-hairline p-md">
              <div className="flex flex-wrap items-center gap-3">
                <Button
                  variant="secondary"
                  onClick={runReconciliation}
                  disabled={busy}
                >
                  Verificar reconciliación
                </Button>
                {reconGreen === true && (
                  <Badge tone="blue">Reconciliación en verde</Badge>
                )}
                {reconGreen === false && (
                  <Badge tone="primary">Reconciliación en rojo</Badge>
                )}
                {reconGreen === null && reconCheckedAt && (
                  <Badge tone="neutral">Se verifica al comprometer</Badge>
                )}
              </div>
              {reconReason && (
                <p className="mt-2 font-body text-body-md text-muted">{reconReason}</p>
              )}
              {reconCheckedAt && (
                <p className="mt-1 font-body text-caption text-muted-label">
                  Última verificación: {formatTime(reconCheckedAt)}
                </p>
              )}
            </div>

            <PoolFacts
              prizeCount={initial.prizeCount}
              activeTicketCount={initial.activeTicketCount}
              raffleStatus={raffle.status}
              drawAt={raffle.drawAt}
            />

            <div className="border-t border-hairline pt-md">
              <Button
                size="lg"
                block
                onClick={doCommit}
                disabled={busy || !checklistComplete}
              >
                Paso 2. Comprometer el sorteo
              </Button>
              {!checklistComplete && (
                <p className="mt-2 font-body text-body-md text-muted">
                  Completa la referencia MININTER, confirma al veedor y verifica la
                  reconciliación para habilitar este paso.
                </p>
              )}
            </div>
          </div>
        </Card>
      )}

      {/* STEP 2 result + STEP 3: Commit shown, reveal available */}
      {(phase === "committed" || phase === "revealed") && commit && (
        <Card elevation="drop" as="section" aria-labelledby="commit-h">
          <h2 id="commit-h" className="font-display text-heading-md font-bold text-ink-strong">
            Valores comprometidos (públicos)
          </h2>
          <p className="mt-1 font-body text-body-md text-muted">
            Estos valores se publicaron antes del sorteo y ya no pueden cambiar.
            Muéstralos en cámara.
          </p>
          <dl className="mt-md grid grid-cols-1 gap-3 md:grid-cols-2">
            <BigValue label="Hash de la semilla" value={commit.serverSeedHash} mono />
            <BigValue label="Ronda drand" value={String(commit.drandRound)} />
            <BigValue label="Hash del snapshot" value={commit.snapshotHash} mono />
            <BigValue label="Boletos en el pool" value={String(commit.snapshotTicketCount)} />
          </dl>
          <p className="mt-2 font-body text-caption text-muted-label">
            La ronda drand publica aproximadamente a las{" "}
            {formatTime(commit.drandRoundPublishesAt)}.
          </p>
        </Card>
      )}

      {phase === "committed" && commit && (
        <Card elevation="drop" as="section" aria-labelledby="reveal-h">
          <h2 id="reveal-h" className="font-display text-heading-md font-bold text-ink-strong">
            Paso 3. Revelar y sortear
          </h2>
          <p className="mt-1 font-body text-body-md text-muted">
            Revela la semilla y obtén la aleatoriedad verificada de drand para la
            ronda comprometida. Los ganadores se seleccionan de forma determinista.
          </p>

          <div className="mt-md flex flex-col gap-3">
            <Button size="lg" block onClick={() => doReveal(false)} disabled={busy}>
              Revelar y sortear (drand automático)
            </Button>

            <button
              type="button"
              className="self-start font-body text-body-md font-bold text-steelblue underline min-h-[44px]"
              onClick={() => setShowManual((v) => !v)}
              aria-expanded={showManual}
            >
              {showManual ? "Ocultar" : "Mostrar"} la ronda manual (si drand no responde)
            </button>

            {showManual && (
              <div className="rounded-md border border-hairline p-md">
                <p className="font-body text-body-md text-muted">
                  Usa esto solo si el sistema no logra obtener la ronda{" "}
                  {commit.drandRound} de drand. Pega los valores de esa ronda desde
                  el explorador público de drand.
                </p>
                <div className="mt-3 flex flex-col gap-3">
                  <Input
                    name="manual-randomness"
                    label={`Randomness de la ronda ${commit.drandRound}`}
                    hint="Hash hexadecimal de 64 caracteres."
                    value={manualRandomness}
                    onChange={(e) => setManualRandomness(e.target.value)}
                  />
                  <Input
                    name="manual-signature"
                    label={`Signature de la ronda ${commit.drandRound}`}
                    hint="Firma BLS en hexadecimal."
                    value={manualSignature}
                    onChange={(e) => setManualSignature(e.target.value)}
                  />
                  <Button
                    variant="secondary"
                    onClick={() => doReveal(true)}
                    disabled={busy || !manualRandomness.trim() || !manualSignature.trim()}
                  >
                    Revelar con la ronda manual
                  </Button>
                </div>
              </div>
            )}
          </div>

          <HoldingScripts />
        </Card>
      )}

      {/* STEP 3 result: winners */}
      {phase === "revealed" && reveal && (
        <Card elevation="drop" as="section" aria-labelledby="winners-h">
          <h2 id="winners-h" className="font-display text-heading-md font-bold text-ink-strong">
            Ganadores
          </h2>
          <table className="mt-md w-full border-collapse text-left">
            <thead>
              <tr className="border-b border-hairline">
                <th scope="col" className="py-2 font-body text-caption uppercase text-muted-label">
                  Orden
                </th>
                <th scope="col" className="py-2 font-body text-caption uppercase text-muted-label">
                  Premio
                </th>
                <th scope="col" className="py-2 font-body text-caption uppercase text-muted-label">
                  Boleto
                </th>
              </tr>
            </thead>
            <tbody>
              {reveal.winners.map((w) => (
                <tr key={w.selectionIndex} className="border-b border-hairline">
                  <td className="py-2 font-body text-body-md text-ink-strong">
                    {w.selectionIndex + 1}
                  </td>
                  <td className="py-2 font-body text-body-md text-ink-strong">
                    {w.prizeName}
                  </td>
                  <td className="py-2 font-impact text-body-lg font-bold text-primary">
                    {w.ticketNo}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>

          <div className="mt-md flex flex-wrap gap-3">
            <Button onClick={loadVerify} disabled={busy}>
              Paso 4. Verificar el sorteo
            </Button>
            <a href={`${apiBase}/winners.csv`} className="inline-flex">
              <Button variant="secondary" type="button">
                Descargar ganadores (CSV)
              </Button>
            </a>
          </div>
        </Card>
      )}

      {/* STEP 4: verification bundle + verdict */}
      {verify && (
        <Card elevation="drop" as="section" aria-labelledby="verify-h">
          <h2 id="verify-h" className="font-display text-heading-md font-bold text-ink-strong">
            Verificación independiente
          </h2>
          {verify.verification ? (
            <>
              <p className="mt-1 font-body text-body-md">
                <span className="font-bold">Resultado: </span>
                {verify.verification.matches
                  ? "El sorteo es reproducible. Los ganadores recalculados coinciden."
                  : "Atención: la verificación encontró una discrepancia. Revisa los detalles."}
              </p>
              <ul className="mt-md flex flex-col gap-2">
                {verify.verification.checks.map((c, i) => (
                  <li key={i} className="font-body text-body-md text-ink-strong">
                    <span className="font-bold">{c.ok ? "OK" : "FALLA"}: </span>
                    {c.label}
                    {c.detail && (
                      <span className="block font-body text-caption text-muted">
                        {c.detail}
                      </span>
                    )}
                  </li>
                ))}
              </ul>
            </>
          ) : (
            <p className="mt-1 font-body text-body-md text-muted">
              El sorteo aún no ha sido revelado. El certificado completo estará
              disponible después de revelar.
            </p>
          )}
          <a
            href={`${apiBase}/verify`}
            className="mt-md inline-flex font-body text-body-md font-bold text-steelblue underline min-h-[44px] items-center"
          >
            Abrir el certificado completo (JSON)
          </a>
        </Card>
      )}

      <LiveIncidentRules />
    </div>
  );
}

function StepPill({
  n,
  label,
  active,
  done,
}: {
  n: number;
  label: string;
  active: boolean;
  done: boolean;
}) {
  const tone = active ? "primary" : done ? "blue" : "neutral";
  return (
    <li>
      <Badge tone={tone}>
        {n}. {label}
        {done ? " (listo)" : active ? " (actual)" : ""}
      </Badge>
    </li>
  );
}

function BigValue({
  label,
  value,
  mono,
}: {
  label: string;
  value: string;
  mono?: boolean;
}) {
  return (
    <div className="rounded-md border border-hairline p-md">
      <dt className="font-body text-caption uppercase text-muted-label">{label}</dt>
      <dd
        className={
          "mt-1 break-all font-bold text-ink-strong " +
          (mono ? "font-mono text-body-md" : "font-impact text-heading-md")
        }
      >
        {value}
      </dd>
    </div>
  );
}

function PoolFacts({
  prizeCount,
  activeTicketCount,
  raffleStatus,
  drawAt,
}: {
  prizeCount: number;
  activeTicketCount: number;
  raffleStatus: string;
  drawAt: string;
}) {
  const closed = raffleStatus === "closed" || raffleStatus === "drawn";
  return (
    <div className="rounded-md border border-hairline p-md">
      <h3 className="font-body text-caption uppercase text-muted-label">
        Estado del pool
      </h3>
      <ul className="mt-2 flex flex-col gap-1 font-body text-body-md text-ink-strong">
        <li>Premios: {prizeCount}</li>
        <li>Boletos activos: {activeTicketCount}</li>
        <li>
          Rifa{" "}
          <span className="font-bold">
            {closed ? "cerrada (lista para comprometer)" : "no cerrada (ciérrala antes de comprometer)"}
          </span>
        </li>
        <li>Sorteo programado: {formatTime(drawAt)}</li>
      </ul>
    </div>
  );
}

function HoldingScripts() {
  return (
    <details className="mt-md rounded-md border border-hairline p-md">
      <summary className="cursor-pointer font-body text-body-md font-bold text-ink-strong min-h-[44px] flex items-center">
        Guion en cámara (si drand demora o hay que pausar)
      </summary>
      <div className="mt-3 flex flex-col gap-md">
        {[DRAND_LATE_SCRIPT, PAUSE_SCRIPT].map((s) => (
          <div key={s.title}>
            <h4 className="font-display text-heading-sm font-bold text-ink-strong">
              {s.title}
            </h4>
            <ul className="mt-2 flex flex-col gap-2">
              {s.lines.map((line, i) => (
                <li key={i} className="font-body text-body-md text-ink-strong">
                  {line}
                </li>
              ))}
            </ul>
          </div>
        ))}
      </div>
    </details>
  );
}

function LiveIncidentRules() {
  return (
    <Card elevation="tight" as="section" aria-labelledby="rules-h" className="border border-hairline">
      <h2 id="rules-h" className="font-display text-heading-sm font-bold text-ink-strong">
        Reglas del sorteo en vivo
      </h2>
      <ul className="mt-3 flex flex-col gap-2">
        {LIVE_INCIDENT_RULES.map((r, i) => (
          <li key={i} className="font-body text-body-md text-ink-strong">
            {r}
          </li>
        ))}
      </ul>
    </Card>
  );
}

function raffleStatusLabel(status: string): string {
  switch (status) {
    case "draft":
      return "Borrador";
    case "open":
      return "Abierta";
    case "closed":
      return "Cerrada";
    case "drawn":
      return "Sorteada";
    default:
      return status;
  }
}

function formatTime(iso: string): string {
  try {
    return new Intl.DateTimeFormat("es-PE", {
      dateStyle: "medium",
      timeStyle: "short",
      timeZone: "America/Lima",
    }).format(new Date(iso));
  } catch {
    return iso;
  }
}
