"use client";

import { useEffect, useMemo, useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { EmptyState } from "@/components/ui/empty-state";
import { SkeletonRows } from "@/components/ui/skeleton";
import { useToast } from "@/components/ui/toast";
import { Tooltip } from "@/components/ui/tooltip";
import { useLocalStorage } from "@/lib/hooks/use-local-storage";
import { formatCurrency, formatDate } from "@/lib/utils";
import { BILL_CATEGORIES } from "@/lib/finance/bill-types";
import { ALL_ORGANIZATIONS } from "@/lib/sag/entities";
import {
  applyOverrides,
  loadOverrides,
  type TxnOverrides,
} from "@/lib/finance/txn-overrides";
import {
  deleteSplit,
  loadSplits,
  saveSplit,
  type TxnSplit,
  type TxnSplitPart,
  type TxnSplits,
} from "@/lib/finance/txn-splits";
import { SplitTxnDialog } from "@/components/app/split-txn-dialog";

interface PlaidAccount {
  accountId: string;
  itemId: string;
  accessToken?: string;
  name: string;
  mask?: string;
  balance?: number | null;
  entitySlug?: string;
}

interface Txn {
  transactionId: string;
  accountId: string;
  date: string;
  amount: number;
  merchantName: string;
  category?: string;
  pending: boolean;
  currency?: string;
}

interface Props {
  /** Optional — when set, filter to accounts assigned to this entity */
  entitySlug?: string;
  /** Default 90 days back */
  days?: number;
}

const DISPLAY_LIMIT = 25;

export function PlaidTransactions({ entitySlug, days = 90 }: Props) {
  const [accounts] = useLocalStorage<PlaidAccount[]>("sag.plaid.accounts", []);
  const [accessTokens] = useLocalStorage<Record<string, string>>("sag.plaid.accessTokens", {});
  const [txns, setTxns] = useState<Txn[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string>("");
  const [fetchedItemIds, setFetchedItemIds] = useState<Set<string>>(new Set());

  // Bulk re-categorize state.
  const [overrides, setOverrides] = useState<TxnOverrides>({});
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
  const [bulkCategory, setBulkCategory] = useState<string>("");
  const [bulkEntity, setBulkEntity] = useState<string>("");
  const { toast } = useToast();

  // Split state — txn-level attribution to multiple entities/categories.
  // When a split exists for a txn, the single-attribution override above is
  // ignored at render time (split wins).
  const [splits, setSplits] = useState<TxnSplits>({});
  const [editingSplitTxnId, setEditingSplitTxnId] = useState<string | null>(null);
  const [openSplitDetailId, setOpenSplitDetailId] = useState<string | null>(null);

  useEffect(() => {
    // eslint-disable-next-line react-hooks/set-state-in-effect -- one-time hydration from localStorage
    setOverrides(loadOverrides());
    setSplits(loadSplits());
  }, []);

  const visibleAccounts = entitySlug
    ? accounts.filter((a) => a.entitySlug === entitySlug)
    : accounts;
  const visibleAccountIds = new Set(visibleAccounts.map((a) => a.accountId));
  const itemIds = Array.from(new Set(visibleAccounts.map((a) => a.itemId)));

  async function fetchAll() {
    setLoading(true);
    setError("");
    const next: Txn[] = [];
    const fetched = new Set<string>();
    try {
      for (const itemId of itemIds) {
        const token = accessTokens[itemId] || accounts.find((a) => a.itemId === itemId)?.accessToken;
        if (!token) continue;
        const resp = await fetch("/api/plaid/transactions", {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({
            accessToken: token,
            startDate: nDaysAgoISO(days),
            endDate: nDaysAgoISO(0),
          }),
        });
        const data = await resp.json();
        if (!resp.ok) throw new Error(data.error || "Plaid request failed");
        next.push(...(data.transactions as Txn[]).filter((t) => visibleAccountIds.has(t.accountId)));
        fetched.add(itemId);
      }
      setTxns(next.sort((a, b) => b.date.localeCompare(a.date)));
      setFetchedItemIds(fetched);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Unknown error");
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    // Defer to next tick to avoid sync-setState-in-effect lint complaint;
    // fetchAll is async and only resolves state updates after I/O.
    const t = setTimeout(() => {
      if (visibleAccounts.length === 0) {
        setTxns([]);
        return;
      }
      void fetchAll();
    }, 0);
    return () => clearTimeout(t);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [entitySlug, visibleAccounts.length]);

  // Overlay overrides onto the raw txn list — the source data is untouched.
  // Splits take precedence: when a split exists for a txn, the single-
  // attribution override is ignored so the row renders as "Multiple" instead
  // of showing a stale single category/entity.
  const visibleTxns = useMemo(() => {
    return txns.slice(0, DISPLAY_LIMIT).map((t) => {
      const split = splits[t.transactionId];
      if (split) {
        return {
          ...t,
          category: t.category,
          entitySlug: undefined as string | undefined,
        };
      }
      const ov = overrides[t.transactionId];
      return {
        ...t,
        category: ov?.category ?? t.category,
        entitySlug: ov?.entitySlug,
      };
    });
  }, [txns, overrides, splits]);

  const visibleIds = useMemo(() => visibleTxns.map((t) => t.transactionId), [visibleTxns]);

  const allVisibleSelected =
    visibleIds.length > 0 && visibleIds.every((id) => selectedIds.has(id));
  const someVisibleSelected =
    !allVisibleSelected && visibleIds.some((id) => selectedIds.has(id));

  function toggleOne(id: string) {
    setSelectedIds((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }

  function toggleAllVisible() {
    setSelectedIds((prev) => {
      const next = new Set(prev);
      if (allVisibleSelected) {
        for (const id of visibleIds) next.delete(id);
      } else {
        for (const id of visibleIds) next.add(id);
      }
      return next;
    });
  }

  function clearSelection() {
    setSelectedIds(new Set());
    setBulkCategory("");
    setBulkEntity("");
  }

  function handleApply() {
    if (selectedIds.size === 0) return;
    if (!bulkCategory && !bulkEntity) return;
    const patch: { category?: string; entitySlug?: string } = {};
    if (bulkCategory) patch.category = bulkCategory;
    if (bulkEntity) patch.entitySlug = bulkEntity;
    const ids = Array.from(selectedIds);
    const next = applyOverrides(ids, patch);
    setOverrides(next);
    const count = ids.length;
    toast({
      title: `Updated ${count} transaction${count === 1 ? "" : "s"}`,
      variant: "success",
    });
    setSelectedIds(new Set());
    setBulkCategory("");
    setBulkEntity("");
  }

  function handleSaveSplit(txnId: string, parts: TxnSplitPart[]) {
    const next = saveSplit(txnId, parts);
    setSplits(next);
    setEditingSplitTxnId(null);
    setOpenSplitDetailId(null);
    toast({
      title: `Split saved across ${parts.length} parts`,
      variant: "success",
    });
  }

  function handleDeleteSplit(txnId: string) {
    const next = deleteSplit(txnId);
    setSplits(next);
    setOpenSplitDetailId((cur) => (cur === txnId ? null : cur));
    setEditingSplitTxnId((cur) => (cur === txnId ? null : cur));
    toast({ title: "Split removed", variant: "info" });
  }

  function toggleSplitDetail(txnId: string) {
    setOpenSplitDetailId((cur) => (cur === txnId ? null : txnId));
  }

  if (visibleAccounts.length === 0) {
    return (
      <EmptyState
        icon="🔌"
        size="compact"
        title="Bank transactions"
        description={
          entitySlug
            ? "No Plaid accounts assigned to this entity. Connect a bank in /app/finance and assign it here."
            : "No Plaid accounts linked. Connect a bank to see transactions."
        }
      />
    );
  }

  const inflows = txns.filter((t) => t.amount < 0).reduce((a, t) => a + Math.abs(t.amount), 0);
  const outflows = txns.filter((t) => t.amount > 0).reduce((a, t) => a + t.amount, 0);

  return (
    <Card>
      <CardContent className="p-5">
        <div className="flex items-center justify-between flex-wrap gap-2 mb-3">
          <div>
            <h3 className="text-base font-semibold">Bank transactions</h3>
            <p className="text-xs text-muted-foreground">
              Last {days} days · {visibleAccounts.length} account{visibleAccounts.length === 1 ? "" : "s"}
            </p>
          </div>
          <div className="flex items-center gap-2">
            <Button variant="outline" size="sm" disabled={loading} onClick={fetchAll}>
              {loading ? "Loading…" : "↻ Refresh"}
            </Button>
          </div>
        </div>

        {error && (
          <div className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-xs text-destructive">
            {error}
          </div>
        )}

        {txns.length > 0 && (
          <div className="grid grid-cols-2 gap-3 mb-4">
            <div className="rounded-md border bg-background p-3">
              <div className="section-label">Inflows</div>
              <div className="text-lg font-semibold text-green-700 dark:text-green-400 tabular-nums">{formatCurrency(inflows)}</div>
            </div>
            <div className="rounded-md border bg-background p-3">
              <div className="section-label">Outflows</div>
              <div className="text-lg font-semibold text-destructive tabular-nums">{formatCurrency(outflows)}</div>
            </div>
          </div>
        )}

        {loading && txns.length === 0 ? (
          <SkeletonRows rows={5} />
        ) : txns.length === 0 ? (
          <p className="text-sm text-muted-foreground">
            {fetchedItemIds.size === 0
              ? "Access token missing for the linked items. Re-connect via /app/finance to enable transactions."
              : "No transactions in this window."}
          </p>
        ) : (
          <div className="overflow-x-auto">
            <table className="w-full min-w-[720px] text-sm">
              <thead className="border-b bg-muted/40">
                <tr>
                  <th className="p-2 w-8">
                    <input
                      type="checkbox"
                      aria-label={`Select all ${visibleIds.length} visible transactions`}
                      checked={allVisibleSelected}
                      ref={(el) => {
                        if (el) el.indeterminate = someVisibleSelected;
                      }}
                      onChange={toggleAllVisible}
                      className="h-4 w-4 cursor-pointer accent-foreground"
                    />
                  </th>
                  <th className="text-left p-2 font-medium">Date</th>
                  <th className="text-left p-2 font-medium">Merchant</th>
                  <th className="text-left p-2 font-medium">Category</th>
                  <th className="text-left p-2 font-medium">Entity</th>
                  <th className="text-right p-2 font-medium">Amount</th>
                  <th className="text-right p-2 font-medium w-24">Actions</th>
                </tr>
              </thead>
              <tbody>
                {visibleTxns.map((t) => {
                  const checked = selectedIds.has(t.transactionId);
                  const split = splits[t.transactionId];
                  const ov = overrides[t.transactionId];
                  const entityLabel = !split && ov?.entitySlug
                    ? ALL_ORGANIZATIONS.find((o) => o.slug === ov.entitySlug)?.name ?? ov.entitySlug
                    : null;
                  const isEditingSplit = editingSplitTxnId === t.transactionId;
                  const isDetailOpen = openSplitDetailId === t.transactionId;
                  return (
                    <SplitRowFragment
                      key={t.transactionId}
                      checked={checked}
                      split={split}
                      isEditingSplit={isEditingSplit}
                      isDetailOpen={isDetailOpen}
                      entityLabel={entityLabel}
                      ov={ov}
                      txn={t}
                      onToggleSelect={() => toggleOne(t.transactionId)}
                      onToggleDetail={() => toggleSplitDetail(t.transactionId)}
                      onOpenSplitEditor={() => {
                        setEditingSplitTxnId(t.transactionId);
                        setOpenSplitDetailId(null);
                      }}
                      onCancelSplitEditor={() => setEditingSplitTxnId(null)}
                      onSaveSplit={(parts) => handleSaveSplit(t.transactionId, parts)}
                      onDeleteSplit={() => handleDeleteSplit(t.transactionId)}
                    />
                  );
                })}
              </tbody>
            </table>
            {txns.length > DISPLAY_LIMIT && (
              <p className="mt-3 text-xs text-muted-foreground text-center">
                Showing {DISPLAY_LIMIT} of {txns.length}. Full export will live in /app/finance once Supabase is wired.
              </p>
            )}
          </div>
        )}

        {selectedIds.size > 0 && (
          <div
            role="region"
            aria-label="Bulk action toolbar"
            className="fixed bottom-4 left-1/2 -translate-x-1/2 z-30 w-[min(95vw,720px)] rounded-lg border bg-background/95 shadow-lg backdrop-blur supports-[backdrop-filter]:bg-background/80"
          >
            <div className="flex flex-wrap items-center gap-2 p-3">
              <Badge variant="secondary" className="text-xs">
                {selectedIds.size} selected
              </Badge>
              <select
                value={bulkCategory}
                onChange={(e) => setBulkCategory(e.target.value)}
                className="h-9 rounded-md border border-input bg-background px-2 text-sm"
                aria-label="Bulk category"
              >
                <option value="">Category...</option>
                {BILL_CATEGORIES.map((c) => (
                  <option key={c} value={c}>
                    {c}
                  </option>
                ))}
              </select>
              <select
                value={bulkEntity}
                onChange={(e) => setBulkEntity(e.target.value)}
                className="h-9 rounded-md border border-input bg-background px-2 text-sm"
                aria-label="Bulk entity tag"
              >
                <option value="">Entity...</option>
                {ALL_ORGANIZATIONS.map((o) => (
                  <option key={o.slug} value={o.slug}>
                    {o.emoji ? `${o.emoji} ` : ""}{o.name}
                  </option>
                ))}
              </select>
              <div className="ml-auto flex items-center gap-2">
                <Button
                  size="sm"
                  onClick={handleApply}
                  disabled={!bulkCategory && !bulkEntity}
                >
                  Apply
                </Button>
                <Button size="sm" variant="ghost" onClick={clearSelection}>
                  Cancel
                </Button>
              </div>
            </div>
          </div>
        )}
      </CardContent>
    </Card>
  );
}

function nDaysAgoISO(n: number): string {
  const d = new Date();
  d.setDate(d.getDate() - n);
  return d.toISOString().slice(0, 10);
}

interface SplitRowFragmentProps {
  checked: boolean;
  split?: TxnSplit;
  isEditingSplit: boolean;
  isDetailOpen: boolean;
  entityLabel: string | null;
  ov: { category?: string; entitySlug?: string } | undefined;
  txn: {
    transactionId: string;
    date: string;
    amount: number;
    merchantName: string;
    category?: string;
    pending: boolean;
    entitySlug?: string;
  };
  onToggleSelect: () => void;
  onToggleDetail: () => void;
  onOpenSplitEditor: () => void;
  onCancelSplitEditor: () => void;
  onSaveSplit: (parts: TxnSplitPart[]) => void;
  onDeleteSplit: () => void;
}

function SplitRowFragment({
  checked,
  split,
  isEditingSplit,
  isDetailOpen,
  entityLabel,
  ov,
  txn,
  onToggleSelect,
  onToggleDetail,
  onOpenSplitEditor,
  onCancelSplitEditor,
  onSaveSplit,
  onDeleteSplit,
}: SplitRowFragmentProps) {
  const hasSplit = Boolean(split);
  const absAmount = Math.abs(txn.amount);
  return (
    <>
      <tr
        className={`border-b hover:bg-muted/20 ${checked ? "bg-muted/30" : ""} ${
          hasSplit && !isEditingSplit ? "border-b-0" : ""
        }`}
      >
        <td className="p-2 align-top">
          <input
            type="checkbox"
            aria-label={`Select transaction at ${txn.merchantName} on ${txn.date}`}
            checked={checked}
            onChange={onToggleSelect}
            className="h-4 w-4 cursor-pointer accent-foreground"
          />
        </td>
        <td className="p-2 text-xs tabular-nums whitespace-nowrap">{formatDate(txn.date)}</td>
        <td className="p-2">
          <div className="font-medium">{txn.merchantName}</div>
          {txn.pending && <Badge variant="warning" className="text-[10px]">Pending</Badge>}
        </td>
        {hasSplit && split ? (
          <td className="p-2 text-xs text-muted-foreground" colSpan={2}>
            <button
              type="button"
              onClick={onToggleDetail}
              aria-expanded={isDetailOpen}
              aria-label={`Show split breakdown for ${txn.merchantName}`}
              className="inline-flex items-center gap-2 rounded-md border border-input bg-background px-2 py-0.5 text-xs hover:bg-muted/40"
            >
              <Badge variant="info" className="text-[10px]">
                Split into {split.parts.length}
              </Badge>
              <span className="text-foreground">Multiple</span>
              <span aria-hidden="true" className="text-muted-foreground">
                {isDetailOpen ? "▴" : "▾"}
              </span>
            </button>
          </td>
        ) : (
          <>
            <td className="p-2 text-xs text-muted-foreground">
              <span className="inline-flex items-center gap-1">
                {txn.category || "—"}
                {ov?.category && (
                  <span
                    className="text-[9px] uppercase tracking-wider text-amber-700 dark:text-amber-400"
                    title="Manually re-categorized"
                  >
                    edited
                  </span>
                )}
              </span>
            </td>
            <td className="p-2 text-xs text-muted-foreground">
              {entityLabel ?? <span className="text-muted-foreground/60">{"—"}</span>}
            </td>
          </>
        )}
        <td
          className={`p-2 text-right font-mono tabular-nums ${
            txn.amount < 0 ? "text-green-700 dark:text-green-400" : ""
          }`}
        >
          {txn.amount < 0 ? "+" : "-"}
          {formatCurrency(absAmount)}
        </td>
        <td className="p-2 text-right">
          <div className="inline-flex items-center gap-1">
            {hasSplit ? (
              <>
                <Button
                  type="button"
                  size="sm"
                  variant="ghost"
                  onClick={onOpenSplitEditor}
                  title="Edit split"
                >
                  Edit split
                </Button>
                <Tooltip content="Remove split">
                  <Button
                    type="button"
                    size="sm"
                    variant="ghost"
                    onClick={onDeleteSplit}
                    aria-label={`Remove split for ${txn.merchantName}`}
                  >
                    &times;
                  </Button>
                </Tooltip>
              </>
            ) : (
              <Button
                type="button"
                size="sm"
                variant="ghost"
                onClick={onOpenSplitEditor}
                title="Split this transaction"
              >
                Split
              </Button>
            )}
          </div>
        </td>
      </tr>
      {hasSplit && split && isDetailOpen && !isEditingSplit && (
        <tr className="bg-muted/20 border-b">
          <td colSpan={7} className="p-3">
            <div className="rounded-md border bg-background p-3">
              <div className="section-label mb-2">
                Split breakdown
              </div>
              <ul className="space-y-1">
                {split.parts.map((p, idx) => {
                  const partAmount = (absAmount * p.percentage) / 100;
                  const ent = p.entitySlug
                    ? ALL_ORGANIZATIONS.find((o) => o.slug === p.entitySlug)?.name ?? p.entitySlug
                    : null;
                  return (
                    <li
                      key={p.id}
                      className="flex items-center justify-between gap-2 text-xs border-b last:border-b-0 py-1"
                    >
                      <div className="flex items-center gap-2">
                        <Badge variant="outline" className="text-[10px]">
                          {p.percentage.toFixed(2)}%
                        </Badge>
                        <span className="text-muted-foreground">Part {idx + 1}</span>
                        {ent && <span className="font-medium">{ent}</span>}
                        {p.category && (
                          <span className="text-muted-foreground">&middot; {p.category}</span>
                        )}
                        {p.note && (
                          <span className="text-muted-foreground italic">&ldquo;{p.note}&rdquo;</span>
                        )}
                      </div>
                      <span className="font-mono tabular-nums text-muted-foreground">
                        {formatCurrency(partAmount)}
                      </span>
                    </li>
                  );
                })}
              </ul>
            </div>
          </td>
        </tr>
      )}
      {isEditingSplit && (
        <tr className="bg-muted/10 border-b">
          <td colSpan={7} className="p-3">
            <SplitTxnDialog
              txn={{
                transactionId: txn.transactionId,
                amount: txn.amount,
                merchantName: txn.merchantName,
              }}
              existing={split}
              onSave={onSaveSplit}
              onCancel={onCancelSplitEditor}
            />
          </td>
        </tr>
      )}
    </>
  );
}
