"use client";

import { useMemo, useState } from "react";
import Link from "next/link";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { BanknoteArrowDown, HandCoins } from "lucide-react";
import { Pagination } from "@/components/shared/pagination";
import { EmptyState, ErrorState, TableSkeleton } from "@/components/shared/states";
import { CashHandInStatusBadge } from "@/components/shared/ledger-badges";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { useCan } from "@/features/auth/use-can";
import { useI18n, useT } from "@/i18n";
import { friendlyErrorMessage } from "@/lib/errors";
import { formatCurrency, formatPhone, formatRelative } from "@/lib/format";
import { queryKeys } from "@/lib/query-keys";
import { useFieldErrors } from "@/lib/use-field-errors";
import { useUrlState } from "@/lib/use-url-state";
import {
  confirmCashHandIn,
  declineCashHandIn,
  getCashHandIns,
  keyedMap,
  toCents,
} from "@/services/ledger";
import type { CashHandIn, CashHandInTotals } from "@/types/ledger";

/**
 * Captains waiting to hand the company's cash in.
 *
 * **Nothing on this screen has moved yet.** Every row is a captain saying they are bringing money,
 * and their balance still carries every riyal of it — which is why the declared figure is drawn as
 * an expectation and never as a settlement. The balance falls when somebody here counts the notes
 * and confirms.
 *
 * **Oldest first**, unlike the balances list: that one leads with the largest amount because it is
 * about who to chase, and this one is a queue of people waiting. Serving the biggest sum first
 * leaves a captain with 40 riyals standing at the counter all afternoon.
 */
export function HandInQueue() {
  const t = useT();
  const { locale } = useI18n();
  const { can } = useCan();
  const canSettle = can("captain_ledger.settle");

  const [page, setPage] = useUrlState("hpage", 1, (value) => {
    const parsed = Number(value);
    return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
  });

  const filters = useMemo(() => ({ page, rows: 15 }), [page]);

  const { data, isLoading, isError, error, refetch } = useQuery({
    queryKey: queryKeys.ledger.handIns(filters),
    queryFn: () => getCashHandIns(filters),
  });

  const handIns = data?.Model ?? [];
  const totals = keyedMap(data?.Totals);
  const currencies = Object.keys(totals).sort();

  // The row whose dialog is open, and which answer it is. Held as the row itself so the dialog
  // acts on the figures the cashier was looking at when they clicked.
  const [answering, setAnswering] = useState<{ handIn: CashHandIn; answer: "confirm" | "decline" } | null>(
    null,
  );

  return (
    <div className="space-y-4">
      {currencies.length > 0 ? (
        <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
          {currencies.map((code) => (
            <ExpectedCard key={code} currency={code} totals={totals[code]} locale={locale} />
          ))}
        </div>
      ) : null}

      <Card>
        <div className="border-b p-4">
          <p className="text-sm text-muted-foreground">{t("handIn.hint")}</p>
        </div>

        {isLoading ? (
          <TableSkeleton rows={6} cols={6} />
        ) : isError ? (
          <ErrorState message={friendlyErrorMessage(error, t)} onRetry={() => refetch()} />
        ) : handIns.length === 0 ? (
          <EmptyState
            title={t("handIn.empty")}
            description={t("handIn.emptyHint")}
            icon={HandCoins}
          />
        ) : (
          <>
            <div className="overflow-x-auto">
              <Table>
                <TableHeader>
                  <TableRow>
                    <TableHead>{t("ledger.captain")}</TableHead>
                    <TableHead className="text-end">{t("handIn.declared")}</TableHead>
                    <TableHead>{t("handIn.waiting")}</TableHead>
                    <TableHead>{t("handIn.captainNote")}</TableHead>
                    <TableHead>{t("common.status")}</TableHead>
                    <TableHead aria-label={t("common.actions")} />
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {handIns.map((row) => (
                    <TableRow key={row.uuid}>
                      <TableCell>
                        {row.captain ? (
                          <>
                            <Link
                              href={`/payments/captains/${row.captain.uuid}`}
                              className="font-medium text-foreground hover:underline"
                            >
                              {row.captain.name}
                            </Link>
                            {row.captain.phone ? (
                              <p className="text-xs text-muted-foreground" dir="ltr">
                                {formatPhone(row.captain.phone)}
                              </p>
                            ) : null}
                          </>
                        ) : (
                          <span className="italic text-muted-foreground">
                            {t("ledger.unknownCaptain")}
                          </span>
                        )}
                      </TableCell>

                      {/*
                        Muted, not emphasised: this is what the captain says they are bringing, and
                        a figure styled like a settled amount would read as money already in.
                      */}
                      <TableCell className="numeric text-end font-medium">
                        {formatCurrency(row.declared_amount, row.currency, locale)}
                      </TableCell>

                      <TableCell className="text-xs text-muted-foreground">
                        {formatRelative(row.created_at, locale)}
                      </TableCell>

                      <TableCell className="max-w-[16rem] text-xs text-muted-foreground">
                        {row.captain_note ?? "—"}
                      </TableCell>

                      <TableCell>
                        <CashHandInStatusBadge status={row.status} label={row.status_label} />
                      </TableCell>

                      <TableCell>
                        <div className="flex items-center justify-end gap-2">
                          {canSettle ? (
                            <>
                              <Button
                                size="sm"
                                onClick={() => setAnswering({ handIn: row, answer: "confirm" })}
                              >
                                {t("handIn.confirm")}
                              </Button>
                              <Button
                                size="sm"
                                variant="outline"
                                onClick={() => setAnswering({ handIn: row, answer: "decline" })}
                              >
                                {t("handIn.decline")}
                              </Button>
                            </>
                          ) : null}
                        </div>
                      </TableCell>
                    </TableRow>
                  ))}
                </TableBody>
              </Table>
            </div>
            <Pagination
              page={data!.Page}
              totalPages={data!.Total}
              records={data!.Records}
              onPageChange={(next) => setPage(next)}
            />
          </>
        )}
      </Card>

      {answering ? (
        <Dialog
          open
          onOpenChange={(open) => {
            if (!open) setAnswering(null);
          }}
        >
          <DialogContent>
            {/*
              The form is its own component so its fields are born with this row's figures. The
              dialog unmounts on close, so a half-typed count can never be carried to the next
              captain in the queue.
            */}
            {answering.answer === "confirm" ? (
              <ConfirmForm handIn={answering.handIn} onDone={() => setAnswering(null)} />
            ) : (
              <DeclineForm handIn={answering.handIn} onDone={() => setAnswering(null)} />
            )}
          </DialogContent>
        </Dialog>
      ) : null}
    </div>
  );
}

/**
 * What one currency's queue should bring in.
 *
 * Labelled as expected rather than held, because that is the whole of what it is: cash somebody
 * has promised to deliver. It sits apart from the balance totals on the other tab for that reason.
 */
function ExpectedCard({
  currency,
  totals,
  locale,
}: {
  currency: string;
  totals: CashHandInTotals;
  locale: "en" | "ar";
}) {
  const t = useT();

  return (
    <Card className="p-4">
      <div className="flex items-start justify-between gap-2">
        <div>
          <p className="text-xs text-muted-foreground">{t("handIn.totals.expected")}</p>
          <p className="mt-1 text-2xl font-semibold text-warning">
            {formatCurrency(totals.declared, currency, locale)}
          </p>
          <p className="mt-0.5 text-xs text-muted-foreground">
            {t("handIn.totals.captains", { count: totals.captains })}
          </p>
        </div>
        <BanknoteArrowDown className="h-5 w-5 text-warning" aria-hidden />
      </div>
    </Card>
  );
}

/**
 * Receive the cash.
 *
 * The amount is pre-filled with the declaration because that is the ordinary case, and it is
 * editable because the interesting case is a short count: typing what was actually counted records
 * the gap and leaves the captain owing it, instead of the difference going unwritten.
 */
function ConfirmForm({ handIn, onDone }: { handIn: CashHandIn; onDone: () => void }) {
  const t = useT();
  const { locale } = useI18n();
  const queryClient = useQueryClient();

  const [amount, setAmount] = useState(String(handIn.declared_amount));
  const [reference, setReference] = useState("");
  const [note, setNote] = useState("");

  const parsed = Number(amount);
  const amountError =
    amount.trim() === "" || Number.isNaN(parsed)
      ? t("ledger.errors.amountRequired")
      : toCents(parsed) < 1
        ? t("ledger.errors.amountPositive")
        : null;

  const fields = useFieldErrors({ only: ["amount", "reference", "note"] });

  const confirm = useMutation({
    mutationFn: () =>
      confirmCashHandIn(handIn.uuid, {
        amount: parsed,
        reference: reference.trim() || undefined,
        note: note.trim() || undefined,
      }),
    onMutate: fields.reset,
    onSuccess: (response) => {
      toast.success(response.Message ?? t("handIn.confirmed"));
      onDone();
      // The queue, the balances and the movement feed all just changed.
      void queryClient.invalidateQueries({ queryKey: queryKeys.ledger.all });
    },
    // The server is the authority: the captain's balance can move between the queue being drawn
    // and this button being pressed, and a count above what they owe is refused there.
    onError: (err) => {
      if (!fields.capture(err)) toast.error(friendlyErrorMessage(err, t));
    },
  });

  const declared = formatCurrency(handIn.declared_amount, handIn.currency, locale);
  const shortBy = toCents(handIn.declared_amount) - toCents(parsed);

  return (
    <>
      <DialogHeader>
        <DialogTitle>{t("handIn.confirmTitle")}</DialogTitle>
        <DialogDescription>
          {t("handIn.confirmBody", { name: handIn.captain?.name ?? "", amount: declared })}
        </DialogDescription>
      </DialogHeader>

      <form
        className="space-y-4"
        onSubmit={(event) => {
          event.preventDefault();
          if (amountError || confirm.isPending) return;
          confirm.mutate();
        }}
      >
        <div className="space-y-1.5">
          <Label htmlFor="hand-in-amount">
            {t("handIn.counted")} ({handIn.currency})
          </Label>
          <Input
            id="hand-in-amount"
            type="number"
            inputMode="decimal"
            step="0.01"
            min="0.01"
            value={amount}
            onChange={(event) => setAmount(event.target.value)}
            aria-invalid={amountError !== null || fields.get("amount") !== undefined}
            autoFocus
          />
          {amountError ?? fields.get("amount") ? (
            <p className="text-xs text-destructive">{amountError ?? fields.get("amount")}</p>
          ) : shortBy > 0 ? (
            // Said in words, because "the captain keeps owing this" is the consequence a cashier
            // needs to see before pressing the button, not after.
            <p className="text-xs text-warning">
              {t("handIn.shortBy", {
                amount: formatCurrency(shortBy / 100, handIn.currency, locale),
              })}
            </p>
          ) : (
            <p className="text-xs text-muted-foreground">{t("handIn.countedHint")}</p>
          )}
        </div>

        <div className="space-y-1.5">
          <Label htmlFor="hand-in-reference">
            {t("ledger.reference")}{" "}
            <span className="text-muted-foreground">({t("common.optional")})</span>
          </Label>
          <Input
            id="hand-in-reference"
            value={reference}
            maxLength={100}
            onChange={(event) => setReference(event.target.value)}
            placeholder={t("ledger.referenceHint")}
            aria-invalid={fields.get("reference") !== undefined}
          />
          {fields.get("reference") ? (
            <p className="text-xs text-destructive">{fields.get("reference")}</p>
          ) : null}
        </div>

        <div className="space-y-1.5">
          <Label htmlFor="hand-in-note">
            {t("ledger.note")}{" "}
            <span className="text-muted-foreground">({t("common.optional")})</span>
          </Label>
          <Input
            id="hand-in-note"
            value={note}
            maxLength={255}
            onChange={(event) => setNote(event.target.value)}
            aria-invalid={fields.get("note") !== undefined}
          />
          {fields.get("note") ? (
            <p className="text-xs text-destructive">{fields.get("note")}</p>
          ) : null}
        </div>

        <DialogFooter>
          <Button type="button" variant="outline" onClick={onDone}>
            {t("common.cancel")}
          </Button>
          <Button type="submit" disabled={amountError !== null || confirm.isPending}>
            {confirm.isPending ? t("common.saving") : t("handIn.confirm")}
          </Button>
        </DialogFooter>
      </form>
    </>
  );
}

/**
 * Turn the declaration away.
 *
 * The reason is required by the server and it is required here for the same purpose: the captain
 * walks out still holding the company's cash, and the next person to open that balance has to be
 * able to read why the last attempt failed. The captain sees it in their app.
 */
function DeclineForm({ handIn, onDone }: { handIn: CashHandIn; onDone: () => void }) {
  const t = useT();
  const { locale } = useI18n();
  const queryClient = useQueryClient();

  const [note, setNote] = useState("");
  const noteError = note.trim().length < 3 ? t("ledger.errors.noteRequired") : null;

  const fields = useFieldErrors({ only: ["note"] });

  const decline = useMutation({
    mutationFn: () => declineCashHandIn(handIn.uuid, { note: note.trim() }),
    onMutate: fields.reset,
    onSuccess: (response) => {
      toast.success(response.Message ?? t("handIn.declined"));
      onDone();
      void queryClient.invalidateQueries({ queryKey: queryKeys.ledger.all });
    },
    onError: (err) => {
      if (!fields.capture(err)) toast.error(friendlyErrorMessage(err, t));
    },
  });

  return (
    <>
      <DialogHeader>
        <DialogTitle>{t("handIn.declineTitle")}</DialogTitle>
        <DialogDescription>
          {t("handIn.declineBody", {
            name: handIn.captain?.name ?? "",
            amount: formatCurrency(handIn.declared_amount, handIn.currency, locale),
          })}
        </DialogDescription>
      </DialogHeader>

      <form
        className="space-y-4"
        onSubmit={(event) => {
          event.preventDefault();
          if (noteError || decline.isPending) return;
          decline.mutate();
        }}
      >
        <div className="space-y-1.5">
          <Label htmlFor="hand-in-decline-note">{t("handIn.reason")}</Label>
          <Input
            id="hand-in-decline-note"
            value={note}
            maxLength={255}
            onChange={(event) => setNote(event.target.value)}
            placeholder={t("handIn.reasonHint")}
            aria-invalid={fields.get("note") !== undefined}
            autoFocus
          />
          {fields.get("note") ? (
            <p className="text-xs text-destructive">{fields.get("note")}</p>
          ) : (
            <p className="text-xs text-muted-foreground">{t("handIn.reasonShown")}</p>
          )}
        </div>

        <DialogFooter>
          <Button type="button" variant="outline" onClick={onDone}>
            {t("common.cancel")}
          </Button>
          <Button
            type="submit"
            variant="destructive"
            disabled={noteError !== null || decline.isPending}
          >
            {decline.isPending ? t("common.saving") : t("handIn.decline")}
          </Button>
        </DialogFooter>
      </form>
    </>
  );
}
