"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import Link from "next/link";
import { AppShell } from "@/components/AppShell";
import { CustomerPicker } from "@/components/CustomerPicker";
import { DiscardBatchCard } from "@/components/DiscardBatchCard";
import {
  confirmStatementBatch,
  discardStatementBatch,
  getStatementBatch,
  listStatementDocuments,
  rejectStatementDocument,
  reviewStatementDocument,
  statementPageUrl,
} from "@/lib/api";
import { useCan } from "@/lib/abilities";
import { formatDate, formatMoney, serviceKindLabel } from "@/lib/labels";
import type {
  ConfirmBatchInput,
  StatementBatchDetail,
  StatementDocument,
  StatementDocumentStatus,
} from "@/lib/types";

/**
 * Review queue for one batch of scanned bills.
 *
 * The reviewer's job is to answer one question per page — "is this the right
 * customer for this amount?" — so the page image sits next to the extracted
 * fields and every row can be corrected in place. Rows the matcher is sure
 * about are pre-approved and can be posted in bulk; everything else is listed
 * first, because that is the work.
 */

const STATUS_LABEL: Record<StatementDocumentStatus, string> = {
  PENDING_OCR: "En proceso",
  OCR_FAILED: "No se pudo leer",
  NEEDS_REVIEW: "Requiere revisión",
  MATCHED: "Identificado",
  CONFIRMED: "Confirmado",
  POSTED: "Registrado",
  REJECTED: "Descartado",
};

/** Rows still needing a decision, listed before the settled ones. */
const OPEN_FIRST: StatementDocumentStatus[] = [
  "NEEDS_REVIEW",
  "OCR_FAILED",
  "MATCHED",
  "CONFIRMED",
  "POSTED",
  "REJECTED",
  "PENDING_OCR",
];

export default function RecibosBatchPage({ params }: { params: { id: string } }) {
  return (
    <AppShell>
      <BatchReview id={params.id} />
    </AppShell>
  );
}

function BatchReview({ id }: { id: string }) {
  const canReview = useCan("statement:review");
  const [batch, setBatch] = useState<StatementBatchDetail | null>(null);
  const [docs, setDocs] = useState<StatementDocument[]>([]);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);
  const [discarding, setDiscarding] = useState(false);

  const load = useCallback(async () => {
    try {
      const [b, d] = await Promise.all([
        getStatementBatch(id),
        listStatementDocuments(id),
      ]);
      setBatch(b);
      setDocs(d);
      setError(null);
    } catch (e) {
      setError((e as Error)?.message ?? "No se pudo cargar el lote.");
    } finally {
      setLoading(false);
    }
  }, [id]);

  useEffect(() => {
    void load();
  }, [load]);

  const processing = batch?.status === "PROCESSING" || batch?.status === "UPLOADED";
  useEffect(() => {
    if (!processing) return;
    const t = setInterval(() => void load(), 4000);
    return () => clearInterval(t);
  }, [processing, load]);

  const sorted = useMemo(
    () =>
      [...docs].sort(
        (a, b) =>
          OPEN_FIRST.indexOf(a.status) - OPEN_FIRST.indexOf(b.status) ||
          a.pageNumber - b.pageNumber,
      ),
    [docs],
  );

  const readyCount = docs.filter(
    (d) => d.status === "MATCHED" && d.matchedCustomer,
  ).length;

  async function discard() {
    setDiscarding(true);
    setError(null);
    try {
      await discardStatementBatch(id);
      await load();
    } catch (e) {
      setError((e as Error)?.message ?? "No se pudo descartar el lote.");
    } finally {
      setDiscarding(false);
    }
  }

  if (loading) return <div className="state-box">Cargando…</div>;
  if (!batch) return <div className="state-box state-error">{error ?? "No encontrado."}</div>;

  const postedCount = batch.byStatus.POSTED ?? 0;
  // Discarding is only offered while the batch can still be abandoned whole:
  // nothing posted to the ledger yet, and not already settled.
  const canDiscard =
    canReview &&
    batch.status !== "DISCARDED" &&
    batch.status !== "COMPLETED" &&
    postedCount === 0;

  return (
    <div className="stack">
      <header className="page-head">
        <div>
          <h1 className="page-title">
            Recibos — {serviceKindLabel(batch.serviceKind)}
            {batch.label ? ` · ${batch.label}` : ""}
          </h1>
          <p className="page-sub">
            {formatDate(batch.createdAt)} · {docs.length} página(s) ·{" "}
            {STATUS_LABEL_BATCH[batch.status] ?? batch.status}
          </p>
        </div>
        <Link className="btn btn-ghost" href="/recibos">
          Volver a captura
        </Link>
      </header>

      {error && <div className="state-box state-error">{error}</div>}

      {processing && (
        <ProcessingBanner docsLength={docs.length} pendingOcr={batch.byStatus.PENDING_OCR ?? 0} />
      )}

      <SummaryCard batch={batch} readyCount={readyCount} />

      {canReview && readyCount > 0 && (
        <ConfirmCard
          batchId={id}
          readyCount={readyCount}
          onDone={load}
          setError={setError}
        />
      )}

      {canDiscard && (
        <DiscardBatchCard
          busy={discarding}
          onDiscard={discard}
          pageCount={docs.length}
          what="recibo"
        />
      )}

      <section className="stack">
        {sorted.map((doc) => (
          <DocumentRow
            key={doc.id}
            doc={doc}
            canReview={canReview}
            onChange={load}
          />
        ))}
      </section>
    </div>
  );
}

const STATUS_LABEL_BATCH: Record<string, string> = {
  UPLOADED: "Recibido",
  PROCESSING: "Procesando",
  READY_FOR_REVIEW: "Listo para revisar",
  COMPLETED: "Registrado",
  FAILED: "Falló",
  DISCARDED: "Descartado",
};

/**
 * Live readout while OCR is running. The backend tells us how many pages are
 * still PENDING_OCR, so we can show real progress instead of "loading…". When
 * the docs list hasn't caught up to the upload yet (total === 0) we fall back
 * to the indeterminate bar.
 */
function ProcessingBanner({
  docsLength,
  pendingOcr,
}: {
  docsLength: number;
  pendingOcr: number;
}) {
  const done = Math.max(docsLength - pendingOcr, 0);
  const pct =
    docsLength > 0 ? Math.min(100, Math.round((done / docsLength) * 100)) : null;
  return (
    <div className="card" style={{ padding: 16 }}>
      <div className="upload-progress" style={{ padding: 0 }}>
        <div
          className={`progress-track${pct === null ? " progress-indeterminate" : ""}`}
          role="progressbar"
          aria-valuemin={0}
          aria-valuemax={100}
          aria-valuenow={pct ?? undefined}
        >
          <div className="progress-fill" style={{ width: `${pct ?? 100}%` }} />
        </div>
        <div className="upload-progress-stats">
          {pct === null ? (
            <span>Leyendo los recibos…</span>
          ) : (
            <>
              <strong>{pct}%</strong>
              <span>
                {done} de {docsLength} página(s) leídas
              </span>
              {pendingOcr > 0 && <span>{pendingOcr} en cola</span>}
            </>
          )}
          <span style={{ marginLeft: "auto" }}>
            Esta pantalla se actualiza sola.
          </span>
        </div>
      </div>
    </div>
  );
}

function SummaryCard({
  batch,
  readyCount,
}: {
  batch: StatementBatchDetail;
  readyCount: number;
}) {
  const entries = Object.entries(batch.byStatus) as [StatementDocumentStatus, number][];
  return (
    <section className="card" style={{ padding: 16 }}>
      <div className="inline-form" style={{ flexWrap: "wrap", gap: 20 }}>
        {entries.map(([status, count]) => (
          <div key={status}>
            <div className="page-sub">{STATUS_LABEL[status] ?? status}</div>
            <div style={{ fontSize: "1.4rem", fontWeight: 600 }}>{count}</div>
          </div>
        ))}
        <div>
          <div className="page-sub">Importe pendiente</div>
          <div style={{ fontSize: "1.4rem", fontWeight: 600 }}>
            {formatMoney(batch.pendingTotal, "MXN")}
          </div>
        </div>
        <div>
          <div className="page-sub">Listos para registrar</div>
          <div style={{ fontSize: "1.4rem", fontWeight: 600 }}>{readyCount}</div>
        </div>
      </div>
    </section>
  );
}

/**
 * Posting is by check, exactly as on the manual capture screen — an OCR batch
 * is still "these bills, paid with this check", so the same fields are asked
 * for and the same ledger path is used.
 */
function ConfirmCard({
  batchId,
  readyCount,
  onDone,
  setError,
}: {
  batchId: string;
  readyCount: number;
  onDone: () => void;
  setError: (m: string | null) => void;
}) {
  const [checkNumber, setCheckNumber] = useState("");
  const [transactionDate, setTransactionDate] = useState(
    new Date().toISOString().slice(0, 10),
  );
  const [outstanding, setOutstanding] = useState(false);
  const [includeReviewed, setIncludeReviewed] = useState(true);
  const [busy, setBusy] = useState(false);
  const [result, setResult] = useState<string | null>(null);

  async function submit() {
    if (!checkNumber.trim()) return;
    setBusy(true);
    setError(null);
    try {
      const input: ConfirmBatchInput = {
        checkNumber: checkNumber.trim(),
        transactionDate,
        outstanding,
        includeReviewed,
      };
      const r = await confirmStatementBatch(batchId, input);
      setResult(
        `Se registraron ${r.posted} movimiento(s) por ${formatMoney(r.total, "MXN")} con el cheque ${r.checkNumber}.`,
      );
      setCheckNumber("");
      onDone();
    } catch (e) {
      setError((e as Error)?.message ?? "No se pudo registrar el lote.");
    } finally {
      setBusy(false);
    }
  }

  return (
    <section className="card" style={{ padding: 16 }}>
      <h2 className="section-title" style={{ marginTop: 0 }}>
        Registrar {readyCount} recibo(s)
      </h2>
      <div className="inline-form" style={{ flexWrap: "wrap", gap: 12 }}>
        <label>
          <span className="page-sub">Cheque</span>
          <input
            className="input"
            value={checkNumber}
            onChange={(e) => setCheckNumber(e.target.value)}
            placeholder="Número de cheque"
          />
        </label>
        <label>
          <span className="page-sub">Fecha</span>
          <input
            type="date"
            className="input"
            value={transactionDate}
            onChange={(e) => setTransactionDate(e.target.value)}
          />
        </label>
        <label className="check">
          <input
            type="checkbox"
            checked={outstanding}
            onChange={(e) => setOutstanding(e.target.checked)}
          />{" "}
          Sin fondos (queda pendiente)
        </label>
        <label className="check">
          <input
            type="checkbox"
            checked={includeReviewed}
            onChange={(e) => setIncludeReviewed(e.target.checked)}
          />{" "}
          Incluir los confirmados a mano
        </label>
        <button
          type="button"
          className="btn btn-primary"
          disabled={!checkNumber.trim() || busy}
          onClick={submit}
        >
          {busy ? "Registrando…" : "Registrar"}
        </button>
      </div>
      {result && (
        <div className="state-box" style={{ marginTop: 12 }}>
          {result}
        </div>
      )}
      <p className="page-sub" style={{ marginTop: 12 }}>
        Se registran como cargos del cliente, por la misma vía que la captura
        manual. Un lote registrado dos veces no duplica cobros.
      </p>
    </section>
  );
}

function DocumentRow({
  doc,
  canReview,
  onChange,
}: {
  doc: StatementDocument;
  canReview: boolean;
  onChange: () => void;
}) {
  const [open, setOpen] = useState(
    doc.status === "NEEDS_REVIEW" || doc.status === "OCR_FAILED",
  );
  const [amount, setAmount] = useState(doc.extractedAmount ?? "");
  const [accountRef, setAccountRef] = useState(doc.extractedAccountRef ?? "");
  const [customerId, setCustomerId] = useState(doc.matchedCustomer?.id ?? "");
  const [customerName, setCustomerName] = useState(doc.matchedCustomer?.name ?? "");
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState<string | null>(null);

  const settled = doc.status === "POSTED" || doc.status === "REJECTED";

  async function save(status: "MATCHED" | "CONFIRMED") {
    setBusy(true);
    setErr(null);
    try {
      await reviewStatementDocument(doc.id, {
        accountRef: accountRef.trim() || undefined,
        amount: amount ? Number(amount) : undefined,
        matchedCustomerId: customerId || undefined,
        status,
      });
      onChange();
    } catch (e) {
      setErr((e as Error)?.message ?? "No se pudo guardar.");
    } finally {
      setBusy(false);
    }
  }

  async function reject() {
    setBusy(true);
    setErr(null);
    try {
      await rejectStatementDocument(doc.id);
      onChange();
    } catch (e) {
      setErr((e as Error)?.message ?? "No se pudo descartar.");
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="card" style={{ padding: 16 }}>
      <div
        className="inline-form"
        style={{ justifyContent: "space-between", flexWrap: "wrap", gap: 12 }}
      >
        <div>
          <strong>Página {doc.pageNumber}</strong>{" "}
          <span className="tag">{STATUS_LABEL[doc.status] ?? doc.status}</span>{" "}
          {doc.provider && <span className="page-sub">{doc.provider}</span>}
          <div className="page-sub" style={{ marginTop: 4 }}>
            {doc.matchedCustomer ? (
              <Link href={`/clientes/${doc.matchedCustomer.id}`}>
                {doc.matchedCustomer.name}
              </Link>
            ) : (
              "Sin cliente asignado"
            )}
            {doc.extractedAccountRef && ` · cuenta ${doc.extractedAccountRef}`}
            {doc.extractedCadastralKey && ` · clave ${doc.extractedCadastralKey}`}
          </div>
          {doc.matchNote && (
            <div className="page-sub" style={{ marginTop: 4 }}>
              {doc.matchNote}
            </div>
          )}
        </div>
        <div className="inline-form" style={{ gap: 8 }}>
          <strong>
            {doc.extractedAmount
              ? formatMoney(doc.extractedAmount, "MXN")
              : "sin importe"}
          </strong>
          <button
            type="button"
            className="btn btn-ghost"
            onClick={() => setOpen((v) => !v)}
          >
            {open ? "Ocultar" : "Ver recibo"}
          </button>
        </div>
      </div>

      {open && (
        <div style={{ marginTop: 12, display: "grid", gap: 16 }}>
          {/* The scan itself — the reviewer's source of truth, not the OCR. */}
          <img
            src={statementPageUrl(doc.id)}
            alt={`Recibo página ${doc.pageNumber}`}
            style={{
              maxWidth: "100%",
              border: "1px solid var(--border, #ddd)",
              borderRadius: 6,
            }}
          />

          {canReview && !settled && (
            <div className="inline-form" style={{ flexWrap: "wrap", gap: 12 }}>
              <label>
                <span className="page-sub">Cuenta</span>
                <input
                  className="input"
                  value={accountRef}
                  onChange={(e) => setAccountRef(e.target.value)}
                />
              </label>
              <label>
                <span className="page-sub">Importe</span>
                <input
                  className="input"
                  inputMode="decimal"
                  value={amount}
                  onChange={(e) => setAmount(e.target.value)}
                />
              </label>
              <div style={{ minWidth: 260 }}>
                <span className="page-sub">Cliente</span>
                <CustomerPicker
                  value={customerId}
                  valueName={customerName}
                  onPick={(cid, name) => {
                    setCustomerId(cid);
                    setCustomerName(name);
                  }}
                />
              </div>
              <button
                type="button"
                className="btn btn-primary"
                disabled={busy || !customerId}
                onClick={() => save("MATCHED")}
              >
                Guardar
              </button>
              <button
                type="button"
                className="btn btn-ghost"
                disabled={busy}
                onClick={reject}
              >
                Descartar
              </button>
            </div>
          )}

          {err && <div className="state-box state-error">{err}</div>}
        </div>
      )}
    </div>
  );
}
