import {
  memo,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ChangeEventHandler,
} from "react";
import { useMutation, usePaginatedQuery, useQuery } from "convex/react";
import {
  flexRender,
  getCoreRowModel,
  useReactTable,
  type ColumnDef,
} from "@tanstack/react-table";
import { useVirtualizer } from "@tanstack/react-virtual";
import { api } from "../../convex/_generated/api";
import type { Doc, Id } from "../../convex/_generated/dataModel";
import { useFilters } from "@/context/FilterContext";
import { useAccountFilterId } from "@/components/layout/AccountFilter";
import { CategoryFilter } from "@/components/layout/CategoryFilter";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { amountClass, formatAmount, formatDate, formatMonth } from "@/lib/format";
import { TransactionFormDialog } from "@/components/transactions/TransactionFormDialog";
import { AssignMonthDialog } from "@/components/transactions/AssignMonthDialog";
import { toast } from "sonner";
import {
  getVisibleSelectionState,
  selectedTransactionsTotal,
  toggleVisibleSelection,
} from "./transactionsSelection";
import { getResetTransactionFilterState } from "@/lib/transactionFilterReset";
import { RotateCcw } from "lucide-react";

type Tx = Doc<"transactions">;
type Category = Doc<"categories">;
const EMPTY_TRANSACTIONS: Tx[] = [];

/* ── Memoized cell components ────────────────────────────────────── */

const RowCheckbox = memo(function RowCheckbox({
  checked,
  indeterminate = false,
  disabled = false,
  ariaLabel,
  onToggle,
}: {
  checked: boolean;
  indeterminate?: boolean;
  disabled?: boolean;
  ariaLabel: string;
  onToggle: ChangeEventHandler<HTMLInputElement>;
}) {
  const ref = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (ref.current) ref.current.indeterminate = indeterminate;
  }, [indeterminate]);

  return (
    <input
      ref={ref}
      type="checkbox"
      checked={checked}
      disabled={disabled}
      aria-label={ariaLabel}
      onChange={onToggle}
      className="h-4 w-4 rounded border-border accent-primary"
    />
  );
});

const AccountCell = memo(function AccountCell({ name }: { name: string | undefined }) {
  return <>{name ?? "–"}</>;
});

const CategoryCell = memo(function CategoryCell({
  tx,
  categories,
  categoryMap,
  onUpdate,
}: {
  tx: Tx;
  categories: Category[] | undefined;
  categoryMap: Map<Id<"categories">, Category>;
  onUpdate: (id: Id<"transactions">, categoryId: Id<"categories">) => void;
}) {
  const [open, setOpen] = useState(false);
  const cat = tx.categoryId ? categoryMap.get(tx.categoryId) : null;

  if (!open) {
    return (
      <button
        type="button"
        onClick={() => setOpen(true)}
        className="inline-flex h-6 items-center rounded px-1.5 text-xs hover:bg-accent"
      >
        {cat ? (
          <Badge style={{ backgroundColor: cat.color, color: "#fff", border: "none" }}>
            {cat.name}
          </Badge>
        ) : (
          <span className="text-muted-foreground">Kategorie…</span>
        )}
      </button>
    );
  }

  return (
    <Select
      defaultOpen
      value={tx.categoryId ?? "none"}
      onValueChange={(v) => {
        if (v !== "none") onUpdate(tx._id, v as Id<"categories">);
        setOpen(false);
      }}
      onOpenChange={(o) => {
        if (!o) setOpen(false);
      }}
    >
      <SelectTrigger className="h-7 w-[130px]">
        <SelectValue placeholder="Kategorie" />
      </SelectTrigger>
      <SelectContent>
        {categories?.map((c) => (
          <SelectItem key={c._id} value={c._id}>
            <span className="inline-flex items-center gap-1.5">
              <span
                className="inline-block h-2.5 w-2.5 rounded-full"
                style={{ backgroundColor: c.color }}
              />
              {c.name}
            </span>
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  );
});

const AssignedCell = memo(function AssignedCell({
  assignedMonth,
  bookingMonth,
}: {
  assignedMonth: string | undefined;
  bookingMonth: string | undefined;
}) {
  if (!assignedMonth || assignedMonth === bookingMonth) return null;
  return <Badge variant="outline">{formatMonth(assignedMonth)}</Badge>;
});

const ActionsCell = memo(function ActionsCell({
  tx,
  onEdit,
  onAssign,
  onRemove,
}: {
  tx: Tx;
  onEdit: (tx: Tx) => void;
  onAssign: (tx: Tx) => void;
  onRemove: (id: Id<"transactions">) => void;
}) {
  return (
    <div className="flex gap-1">
      <Button size="sm" variant="ghost" onClick={() => onEdit(tx)}>
        Bearbeiten
      </Button>
      <Button size="sm" variant="ghost" onClick={() => onAssign(tx)}>
        Monat…
      </Button>
      <Button size="sm" variant="ghost" onClick={() => onRemove(tx._id)}>
        Löschen
      </Button>
    </div>
  );
});

/* ── Page ────────────────────────────────────────────────────────── */

export function TransactionsPage() {
  const [search, setSearch] = useState("");
  const [type, setType] = useState<"all" | "einnahme" | "ausgabe">("all");
  const [pendingOnly, setPendingOnly] = useState(false);
  const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
  const [editTx, setEditTx] = useState<Tx | null>(null);
  const [assignTx, setAssignTx] = useState<Tx | null>(null);
  const [createOpen, setCreateOpen] = useState(false);

  const {
    categoryIds,
    from,
    to,
    monthBasis,
    setPreset,
    setAccountId,
    setCategoryIds,
    setMonthBasis,
  } = useFilters();
  const accountId = useAccountFilterId();

  const categories = useQuery(api.categories.list);
  const accounts = useQuery(api.accounts.list);
  const removeTx = useMutation(api.transactions.remove);
  const updateTx = useMutation(api.transactions.update);
  const bulkCategory = useMutation(api.transactions.bulkSetCategory);

  const { results, status, loadMore } = usePaginatedQuery(
    api.transactions.list,
    {
      search: search || undefined,
      from,
      to,
      accountId,
      basis: monthBasis,
      type: type === "all" ? undefined : type,
      pendingOnly: pendingOnly || undefined,
      categoryIds:
        categoryIds.length > 0
          ? (categoryIds.filter((id) => id !== "__none__") as Id<"categories">[])
          : undefined,
      withoutCategory: categoryIds.includes("__none__") || undefined,
    },
    { initialNumItems: 50 },
  );

  const categoryMap = useMemo(
    () => new Map(categories?.map((c) => [c._id, c])),
    [categories],
  );
  const accountMap = useMemo(
    () => new Map(accounts?.map((a) => [a._id, a.name])),
    [accounts],
  );
  const visibleTransactions = results ?? EMPTY_TRANSACTIONS;
  const visibleSelectionState = useMemo(
    () => getVisibleSelectionState(visibleTransactions, rowSelection),
    [visibleTransactions, rowSelection],
  );
  const selectedTotal = useMemo(
    () => selectedTransactionsTotal(visibleTransactions, rowSelection),
    [visibleTransactions, rowSelection],
  );

  const handleUpdateCategory = useCallback(
    (id: Id<"transactions">, categoryId: Id<"categories">) => {
      void updateTx({ id, categoryId });
    },
    [updateTx],
  );
  const handleEdit = useCallback((tx: Tx) => setEditTx(tx), []);
  const handleAssign = useCallback((tx: Tx) => setAssignTx(tx), []);
  const handleRemove = useCallback(
    async (id: Id<"transactions">) => {
      if (confirm("Transaktion löschen?")) {
        await removeTx({ id });
        toast.success("Gelöscht");
      }
    },
    [removeTx],
  );
  const handleToggleAllVisible = useCallback(
    (checked: boolean) => {
      setRowSelection((selection) =>
        toggleVisibleSelection(selection, visibleTransactions, checked),
      );
    },
    [visibleTransactions],
  );
  const handleResetFilters = useCallback(() => {
    const reset = getResetTransactionFilterState();

    setPreset(reset.global.preset);
    setAccountId(reset.global.accountId);
    setCategoryIds([...reset.global.categoryIds]);
    setMonthBasis(reset.global.monthBasis);
    setSearch(reset.page.search);
    setType(reset.page.type);
    setPendingOnly(reset.page.pendingOnly);
    setRowSelection(reset.page.rowSelection);
  }, [setAccountId, setCategoryIds, setMonthBasis, setPreset]);

  const selectedIds = useMemo(
    () =>
      visibleTransactions
        .filter((tx) => rowSelection[tx._id])
        .map((tx) => tx._id),
    [visibleTransactions, rowSelection],
  );

  const columns = useMemo<ColumnDef<Tx>[]>(
    () => [
      {
        id: "select",
        header: () => (
          <RowCheckbox
            checked={visibleSelectionState.allSelected}
            indeterminate={
              visibleSelectionState.someSelected && !visibleSelectionState.allSelected
            }
            disabled={visibleTransactions.length === 0}
            ariaLabel={
              visibleSelectionState.allSelected
                ? "Alle sichtbaren Transaktionen abwählen"
                : "Alle sichtbaren Transaktionen auswählen"
            }
            onToggle={(event) => handleToggleAllVisible(event.target.checked)}
          />
        ),
        cell: ({ row }) => (
          <RowCheckbox
            checked={row.getIsSelected()}
            ariaLabel="Transaktion auswählen"
            onToggle={(event) => row.getToggleSelectedHandler()(event)}
          />
        ),
      },
      {
        accessorKey: "bookingDate",
        header: "Datum",
        cell: ({ row }) =>
          row.original.isPending ? "offen" : formatDate(row.original.bookingDate),
      },
      { accessorKey: "description", header: "Beschreibung" },
      { accessorKey: "counterparty", header: "Gegenpartei" },
      {
        id: "account",
        header: "Konto",
        cell: ({ row }) => (
          <AccountCell
            name={row.original.accountId ? accountMap.get(row.original.accountId) : undefined}
          />
        ),
      },
      {
        id: "category",
        header: "Kategorie",
        cell: ({ row }) => (
          <CategoryCell
            tx={row.original}
            categories={categories}
            categoryMap={categoryMap}
            onUpdate={handleUpdateCategory}
          />
        ),
      },
      {
        accessorKey: "amount",
        header: () => <span className="float-right">Betrag</span>,
        cell: ({ row }) => (
          <span className={`float-right font-medium ${amountClass(row.original.amount)}`}>
            {formatAmount(row.original.amount)}
          </span>
        ),
      },
      {
        id: "assigned",
        header: "Zuordnung",
        cell: ({ row }) => {
          const bookingMonth = row.original.bookingDate?.slice(0, 7);
          return (
            <AssignedCell assignedMonth={row.original.assignedMonth} bookingMonth={bookingMonth} />
          );
        },
      },
      {
        id: "actions",
        header: "",
        cell: ({ row }) => (
          <ActionsCell
            tx={row.original}
            onEdit={handleEdit}
            onAssign={handleAssign}
            onRemove={handleRemove}
          />
        ),
      },
    ],
    [
      categories,
      categoryMap,
      accountMap,
      handleUpdateCategory,
      handleEdit,
      handleAssign,
      handleRemove,
      visibleSelectionState,
      visibleTransactions.length,
      handleToggleAllVisible,
    ],
  );

  const table = useReactTable({
    data: visibleTransactions,
    columns,
    state: { rowSelection },
    onRowSelectionChange: setRowSelection,
    getRowId: (row) => row._id,
    getCoreRowModel: getCoreRowModel(),
  });

  /* ── Virtualization ── */
  const parentRef = useRef<HTMLDivElement>(null);
  const rows = table.getRowModel().rows;

  const rowVirtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 44,
    overscan: 10,
  });

  const virtualRows = rowVirtualizer.getVirtualItems();
  const totalSize = rowVirtualizer.getTotalSize();
  const paddingTop = virtualRows.length > 0 ? virtualRows[0].start : 0;
  const paddingBottom =
    virtualRows.length > 0 ? totalSize - virtualRows[virtualRows.length - 1].end : 0;

  /* ── Auto-load more when scrolling near the end ── */
  useEffect(() => {
    if (status !== "CanLoadMore") return;
    const last = virtualRows[virtualRows.length - 1];
    if (last && last.index >= rows.length - 10) {
      loadMore(50);
    }
  }, [virtualRows, rows.length, status, loadMore]);

  return (
    <div className="flex h-[calc(100vh-8rem)] min-h-0 flex-col gap-4">
      <div className="flex flex-wrap items-center gap-2">
        <Input
          placeholder="Suche…"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          className="max-w-xs"
        />
        <Select value={type} onValueChange={(v) => setType(v as typeof type)}>
          <SelectTrigger className="w-[140px]">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="all">Alle Typen</SelectItem>
            <SelectItem value="einnahme">Einnahmen</SelectItem>
            <SelectItem value="ausgabe">Ausgaben</SelectItem>
          </SelectContent>
        </Select>
        <CategoryFilter />
        <label className="flex items-center gap-2 text-sm">
          <input
            type="checkbox"
            checked={pendingOnly}
            onChange={(e) => setPendingOnly(e.target.checked)}
          />
          Nur offene
        </label>
        <Button variant="outline" onClick={handleResetFilters}>
          <RotateCcw className="h-4 w-4" />
          Zurücksetzen
        </Button>
        <Button onClick={() => setCreateOpen(true)}>Neu</Button>
        {visibleSelectionState.someSelected && (
          <div className="flex h-9 items-center gap-2 rounded-md border bg-muted/40 px-3 text-sm">
            <span className="font-medium">
              {visibleSelectionState.selectedCount} ausgewählt
            </span>
            <span className="text-muted-foreground">Summe</span>
            <span className={`font-semibold tabular-nums ${amountClass(selectedTotal)}`}>
              {formatAmount(selectedTotal)}
            </span>
          </div>
        )}
        {selectedIds.length > 0 && categories && (
          <Select
            onValueChange={async (categoryId) => {
              await bulkCategory({
                ids: selectedIds,
                categoryId: categoryId as Id<"categories">,
              });
              toast.success("Kategorie zugewiesen");
              setRowSelection({});
            }}
          >
            <SelectTrigger className="w-[200px]">
              <SelectValue placeholder="Bulk-Kategorie" />
            </SelectTrigger>
            <SelectContent>
              {categories.map((c) => (
                <SelectItem key={c._id} value={c._id}>
                  {c.name}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        )}
      </div>

      <div className="min-h-0 flex-1 rounded-md border">
        <div ref={parentRef} className="relative h-full w-full overflow-auto">
          <table className="w-full caption-bottom text-sm">
            <TableHeader>
              {table.getHeaderGroups().map((hg) => (
                <TableRow key={hg.id}>
                  {hg.headers.map((h) => (
                    <TableHead key={h.id} className="sticky top-0 z-10 bg-background">
                      {flexRender(h.column.columnDef.header, h.getContext())}
                    </TableHead>
                  ))}
                </TableRow>
              ))}
            </TableHeader>
            <TableBody>
              {paddingTop > 0 && <tr style={{ height: paddingTop }} />}
              {virtualRows.map((virtualRow) => {
                const row = rows[virtualRow.index];
                return (
                  <tr
                    key={row.id}
                    data-index={virtualRow.index}
                    ref={(node: HTMLTableRowElement | null) =>
                      rowVirtualizer.measureElement(node)
                    }
                    className="border-b transition-colors hover:bg-muted/50"
                  >
                    {row.getVisibleCells().map((cell) => (
                      <TableCell key={cell.id}>
                        {flexRender(cell.column.columnDef.cell, cell.getContext())}
                      </TableCell>
                    ))}
                  </tr>
                );
              })}
              {paddingBottom > 0 && <tr style={{ height: paddingBottom }} />}
            </TableBody>
          </table>
        </div>
      </div>

      <TransactionFormDialog open={createOpen} onOpenChange={setCreateOpen} />
      <TransactionFormDialog
        open={!!editTx}
        onOpenChange={(o) => !o && setEditTx(null)}
        transaction={editTx ?? undefined}
      />
      <AssignMonthDialog transaction={assignTx} onClose={() => setAssignTx(null)} />
    </div>
  );
}
