import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useRef, useState } from "react";
import { Archive, ArchiveRestore, Loader2, Pencil, PiggyBank, Trash2, Undo2 } from "lucide-react";
import {
  Area,
  AreaChart,
  CartesianGrid,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";
import { toast } from "sonner";
import { AppShell } from "@/components/app/AppShell";
import { PlanFormDialog } from "@/components/app/PlanFormDialog";
import { DepositDialog } from "@/components/app/DepositDialog";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress";
import {
  AlertDialog,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogCancel,
} from "@/components/ui/alert-dialog";
import { ApiError, plansApi, type Deposit, type Plan } from "@/lib/api";
import { CURRENCIES, FREQ, fmt, fmtDate, fmtPercent } from "@/lib/money";
import { invalidateSavings, keys } from "@/lib/queries";

export const Route = createFileRoute("/plans/$id")({
  head: () => ({
    meta: [
      { title: "Detail plan — NABUNG" },
      { name: "description", content: "Detail rencana tabungan dan riwayat setoran." },
      { property: "og:title", content: "Detail plan NABUNG" },
      { property: "og:description", content: "Progres dan riwayat setoran." },
      { property: "og:type", content: "website" },
      { name: "twitter:card", content: "summary" },
    ],
  }),
  component: PlanPage,
});

function PlanPage() {
  const { id } = Route.useParams();
  const nav = useNavigate();
  const qc = useQueryClient();
  const q = useQuery({ queryKey: keys.plan(id), queryFn: () => plansApi.get(id) });
  const progressQ = useQuery({ queryKey: keys.progress(id), queryFn: () => plansApi.progress(id) });
  const depQ = useInfiniteQuery({
    queryKey: keys.deposits(id),
    queryFn: ({ pageParam }) => plansApi.deposits(id, { page: pageParam, per_page: 20 }),
    initialPageParam: 1,
    getNextPageParam: (last) =>
      last.meta.current_page < last.meta.last_page ? last.meta.current_page + 1 : undefined,
  });
  const [edit, setEdit] = useState(false);
  const [dep, setDep] = useState<Plan | null>(null);
  const [confirmDelete, setConfirmDelete] = useState(false);
  const [reversing, setReversing] = useState<Deposit | null>(null);
  // One key per reversal dialog so a retry after a timeout cannot reverse twice.
  const reverseKey = useRef("");

  const del = useMutation({
    mutationFn: () => plansApi.remove(id),
    onSuccess: () => {
      qc.removeQueries({ queryKey: keys.plan(id) });
      invalidateSavings(qc);
      nav({ to: "/dashboard" });
    },
  });
  const archive = useMutation({
    mutationFn: (status: "active" | "archived") => plansApi.update(id, { status }),
    onSuccess: (saved) => {
      qc.setQueryData(keys.plan(id), saved);
      invalidateSavings(qc, id);
      toast.success(saved.status === "archived" ? "Plan diarsipkan" : "Plan diaktifkan kembali");
    },
    onError: (e) => toast.error((e as Error).message),
  });
  const reverse = useMutation({
    mutationFn: (d: Deposit) => plansApi.reverse(id, d.id, {}, reverseKey.current),
    onSuccess: ({ plan }) => {
      qc.setQueryData(keys.plan(id), plan);
      invalidateSavings(qc, id);
      toast.success("Setoran dibatalkan");
      setReversing(null);
    },
  });
  const p = q.data;

  if (!p) {
    const notFound = q.error instanceof ApiError && q.error.status === 404;
    return (
      <AppShell title="Plan" back>
        {q.isError ? (
          <div role="alert" className="space-y-3">
            <p className="text-destructive">
              {notFound ? "Plan tidak ditemukan." : q.error.message}
            </p>
            {!notFound && (
              <Button variant="outline" onClick={() => q.refetch()}>
                Coba lagi
              </Button>
            )}
          </div>
        ) : (
          <div className="h-64 animate-pulse rounded-2xl bg-muted" />
        )}
      </AppShell>
    );
  }

  const est = p.estimate;
  const done = p.status === "completed" || p.remaining_minor === 0;
  const dec = CURRENCIES[p.currency].decimals;
  const compact = new Intl.NumberFormat(CURRENCIES[p.currency].locale, { notation: "compact" });
  const series = (progressQ.data ?? []).map((d) => ({
    date: fmtDate(d.date),
    total: d.deposited_minor,
  }));
  const deposits = depQ.data?.pages.flatMap((x) => x.deposits) ?? [];
  const sched = est.schedule && est.schedule.state ? est.schedule : null;
  const SCHED = {
    ahead: "Lebih cepat dari rencana",
    on_track: "Sesuai rencana",
    behind: "Tertinggal dari rencana",
  } as const;

  return (
    <AppShell
      title={p.name}
      back
      actions={
        <div className="flex flex-wrap gap-2">
          <Button variant="outline" onClick={() => setEdit(true)}>
            <Pencil /> Ubah
          </Button>
          {p.status !== "completed" && (
            <Button
              variant="outline"
              disabled={archive.isPending}
              onClick={() => archive.mutate(p.status === "archived" ? "active" : "archived")}
            >
              {archive.isPending ? (
                <Loader2 className="animate-spin" />
              ) : p.status === "archived" ? (
                <ArchiveRestore />
              ) : (
                <Archive />
              )}{" "}
              {p.status === "archived" ? "Aktifkan" : "Arsipkan"}
            </Button>
          )}
          <Button
            variant="outline"
            disabled={del.isPending}
            onClick={() => {
              del.reset();
              setConfirmDelete(true);
            }}
          >
            <Trash2 /> Hapus
          </Button>
          <Button variant="hero" onClick={() => setDep(p)} disabled={p.status !== "active"}>
            <PiggyBank /> Setor
          </Button>
        </div>
      }
    >
      <section className="grid gap-4 rounded-3xl bg-gradient-ink p-6 text-ink-foreground md:grid-cols-4">
        <div className="md:col-span-4">
          <div className="flex justify-between text-sm">
            <span>
              {FREQ[p.frequency].label} · {fmt(p.planned_minor, p.currency)}/{FREQ[p.frequency].per}
            </span>
            <b className="tabular">{fmtPercent(p.progress_percent)}</b>
          </div>
          <Progress
            value={p.progress_percent}
            className="mt-2 h-3 bg-ink-foreground/15 [&>div]:bg-gold"
          />
          <div className="mt-2 flex flex-wrap gap-2">
            {p.status === "archived" && <Badge variant="secondary">Diarsipkan</Badge>}
            {p.status === "completed" && <Badge>Selesai</Badge>}
            {p.is_overdue && <Badge variant="destructive">Lewat tenggat</Badge>}
          </div>
        </div>
        {[
          ["Target", fmt(p.target_minor, p.currency)],
          ["Terkumpul", fmt(p.deposited_minor, p.currency)],
          ["Sisa", fmt(p.remaining_minor, p.currency)],
          [
            "Estimasi selesai",
            done
              ? "Tercapai"
              : est.estimated_completion_date
                ? fmtDate(est.estimated_completion_date)
                : "—",
          ],
        ].map(([k, v]) => (
          <div key={k}>
            <p className="text-xs text-ink-foreground/60">{k}</p>
            <p className="font-display text-xl font-semibold tabular">{v}</p>
          </div>
        ))}
        {p.surplus_minor > 0 && (
          <div className="md:col-span-4 text-sm text-ink-foreground/80">
            Kelebihan setoran: <b className="tabular">{fmt(p.surplus_minor, p.currency)}</b>
          </div>
        )}
        {!done && p.status === "active" && (
          <dl className="grid gap-x-6 gap-y-1 text-sm text-ink-foreground/80 md:col-span-4 md:grid-cols-2">
            <div className="flex justify-between gap-2">
              <dt>Sisa periode</dt>
              <dd className="font-semibold tabular">
                {est.remaining_periods} {FREQ[p.frequency].per}
              </dd>
            </div>
            {p.target_date && (
              <div className="flex justify-between gap-2">
                <dt>Tanggal target</dt>
                <dd className="font-semibold">{fmtDate(p.target_date)}</dd>
              </div>
            )}
            {est.required_minor_for_target_date !== null && (
              <div className="flex justify-between gap-2">
                <dt>Perlu per {FREQ[p.frequency].per} agar tepat waktu</dt>
                <dd className="font-semibold tabular text-gold">
                  {fmt(est.required_minor_for_target_date, p.currency)}
                </dd>
              </div>
            )}
            {sched && (
              <div className="flex justify-between gap-2">
                <dt>{SCHED[sched.state!]}</dt>
                <dd className="font-semibold tabular">
                  {sched.delta_minor === 0
                    ? "sesuai"
                    : `${sched.delta_minor > 0 ? "+" : "−"}${fmt(Math.abs(sched.delta_minor), p.currency)}`}
                </dd>
              </div>
            )}
            {sched && (
              <div className="flex justify-between gap-2">
                <dt>Rencana sampai hari ini</dt>
                <dd className="font-semibold tabular">
                  {fmt(sched.planned_to_date_minor, p.currency)}
                </dd>
              </div>
            )}
          </dl>
        )}
      </section>
      <section className="mt-6 grid gap-6 lg:grid-cols-[1.5fr_1fr]">
        <div className="rounded-2xl border bg-card p-5 shadow-card">
          <h2 className="text-lg font-semibold">Progres setoran</h2>
          {progressQ.isError ? (
            <div role="alert" className="mt-4 space-y-2 text-sm">
              <p className="text-destructive">{progressQ.error.message}</p>
              <Button size="sm" variant="outline" onClick={() => progressQ.refetch()}>
                Coba lagi
              </Button>
            </div>
          ) : progressQ.isPending ? (
            <div className="mt-4 h-64 animate-pulse rounded-xl bg-muted" />
          ) : series.length ? (
            <div className="mt-4 h-64">
              <ResponsiveContainer>
                <AreaChart data={series}>
                  <CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
                  <XAxis dataKey="date" fontSize={11} stroke="var(--muted-foreground)" />
                  <YAxis
                    fontSize={11}
                    stroke="var(--muted-foreground)"
                    width={70}
                    tickFormatter={(v: number) => compact.format(v / 10 ** dec)}
                  />
                  <Tooltip formatter={(v: number) => fmt(v, p.currency)} />
                  <Area dataKey="total" stroke="var(--primary)" fill="var(--accent)" />
                </AreaChart>
              </ResponsiveContainer>
            </div>
          ) : (
            <p className="mt-6 text-sm text-muted-foreground">
              Belum ada setoran. Grafik muncul setelah setoran pertama.
            </p>
          )}
        </div>
        <div className="rounded-2xl border bg-card p-5 shadow-card">
          <h2 className="text-lg font-semibold">Riwayat</h2>
          {depQ.isError ? (
            <div role="alert" className="mt-3 space-y-2 text-sm">
              <p className="text-destructive">{depQ.error.message}</p>
              <Button size="sm" variant="outline" onClick={() => depQ.refetch()}>
                Coba lagi
              </Button>
            </div>
          ) : depQ.isPending ? (
            <div className="mt-3 h-24 animate-pulse rounded-xl bg-muted" />
          ) : (
            <ul className="mt-3 divide-y">
              {deposits.map((d) => {
                const isRev = d.type === "reversal";
                return (
                  <li key={d.id} className="flex items-start justify-between gap-2 py-2 text-sm">
                    <span className="min-w-0">
                      <span className="block">
                        {fmtDate(d.created_at)}{" "}
                        {isRev && (
                          <Badge variant="destructive" className="ml-1">
                            Pembatalan
                          </Badge>
                        )}
                        {d.reversed && (
                          <Badge variant="secondary" className="ml-1">
                            Dibatalkan
                          </Badge>
                        )}
                      </span>
                      {d.note && <span className="text-xs text-muted-foreground">{d.note}</span>}
                    </span>
                    <span className="flex shrink-0 items-center gap-1">
                      <b
                        className={`tabular ${isRev ? "text-destructive" : d.reversed ? "text-muted-foreground line-through" : ""}`}
                      >
                        {isRev ? "−" : "+"}
                        {fmt(d.amount_minor, p.currency)}
                      </b>
                      {!isRev && !d.reversed && (
                        <Button
                          variant="ghost"
                          size="icon"
                          className="size-7"
                          aria-label={`Batalkan setoran ${fmt(d.amount_minor, p.currency)}`}
                          onClick={() => {
                            reverse.reset();
                            reverseKey.current = crypto.randomUUID();
                            setReversing(d);
                          }}
                        >
                          <Undo2 className="size-4" />
                        </Button>
                      )}
                    </span>
                  </li>
                );
              })}
              {!deposits.length && (
                <li className="py-2 text-sm text-muted-foreground">Belum ada setoran.</li>
              )}
            </ul>
          )}
          {depQ.hasNextPage && (
            <Button
              variant="outline"
              size="sm"
              className="mt-3 w-full"
              disabled={depQ.isFetchingNextPage}
              onClick={() => depQ.fetchNextPage()}
            >
              {depQ.isFetchingNextPage && <Loader2 className="animate-spin" />} Muat lebih banyak
            </Button>
          )}
        </div>
      </section>
      <PlanFormDialog open={edit} onOpenChange={setEdit} plan={p} />
      <DepositDialog plan={dep} onOpenChange={(o) => !o && setDep(null)} />
      <AlertDialog
        open={confirmDelete}
        onOpenChange={(open) => !del.isPending && setConfirmDelete(open)}
      >
        <AlertDialogContent className="max-w-[calc(100%-2rem)] sm:max-w-md">
          <AlertDialogHeader>
            <AlertDialogTitle>Hapus plan?</AlertDialogTitle>
            <AlertDialogDescription>
              Plan “{p.name}” akan dihapus dari daftarmu. Catatan setorannya tetap disimpan untuk
              audit dan tidak dapat diubah.
            </AlertDialogDescription>
          </AlertDialogHeader>
          {del.isError && (
            <p role="alert" className="text-sm text-destructive">
              {del.error.message}
            </p>
          )}
          <AlertDialogFooter>
            <AlertDialogCancel disabled={del.isPending}>Batal</AlertDialogCancel>
            <Button variant="destructive" disabled={del.isPending} onClick={() => del.mutate()}>
              <Trash2 /> {del.isPending ? "Menghapus…" : "Hapus plan"}
            </Button>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
      <AlertDialog
        open={!!reversing}
        onOpenChange={(open) => !reverse.isPending && !open && setReversing(null)}
      >
        <AlertDialogContent className="max-w-[calc(100%-2rem)] sm:max-w-md">
          <AlertDialogHeader>
            <AlertDialogTitle>Batalkan setoran?</AlertDialogTitle>
            <AlertDialogDescription>
              Setoran {reversing ? fmt(reversing.amount_minor, p.currency) : ""} akan dibatalkan
              dengan mencatat entri pembatalan. Riwayat asli tidak dihapus.
            </AlertDialogDescription>
          </AlertDialogHeader>
          {reverse.isError && (
            <p role="alert" className="text-sm text-destructive">
              {reverse.error.message}
            </p>
          )}
          <AlertDialogFooter>
            <AlertDialogCancel disabled={reverse.isPending}>Batal</AlertDialogCancel>
            <Button
              variant="destructive"
              disabled={reverse.isPending}
              onClick={() => reversing && !reverse.isPending && reverse.mutate(reversing)}
            >
              {reverse.isPending ? <Loader2 className="animate-spin" /> : <Undo2 />}{" "}
              {reverse.isPending
                ? "Membatalkan…"
                : reverse.isError
                  ? "Coba lagi"
                  : "Batalkan setoran"}
            </Button>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </AppShell>
  );
}
