import { useState, useEffect, useCallback, useMemo } from 'react';
import { toast } from 'sonner';
import {
  AlertTriangle, Building2, ChevronDown, ChevronRight,
  Eye, EyeOff, ExternalLink, History, Landmark, Link2Off, Loader2, RefreshCw, Unlink,
} from 'lucide-react';
import { api } from '@/api';
import { cn, errMessage, parseUtc } from '@/lib/utils';
import { formatUSD, formatCentsUSD, type Cents, type Dollars } from '@/lib/money';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Switch } from '@/components/ui/switch';
import {
  AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
  AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import {
  Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
} from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { SectionCard, type SectionCardProps } from './dataShared';
import AutoMatchReview from './AutoMatchReview';
import type { Bill } from '@/types';

interface BankTx {
  id: number | string;
  posted_date?: string | null;
  transacted_at?: string | null;
  payee?: string | null;
  description?: string | null;
  amount: Cents;
  match_status?: string | null;
  matched_bill_id?: number | null;
  matched_bill_name?: string | null;
}

interface BankAccount {
  id: number | string;
  name: string;
  org_name?: string | null;
  monitored?: boolean;
  balance: Cents;
  transaction_count?: number;
  transactions: BankTx[];
}

interface Connection {
  id: number | string;
  name?: string;
  provider?: string;
  status?: string;
  last_error?: string | null;
  last_sync_at?: string | null;
  account_count?: number;
  transaction_count?: number;
}

interface FinAccount {
  id: number | string;
  name: string;
  org_name?: string | null;
  balance_dollars?: Dollars | null;
}

interface BtPatch {
  enabled?: boolean;
  accountId?: string;
  pendingDays?: number;
  lateGraceDays?: number;
}

function TokenInput({ value, onChange, disabled }: {
  value: string;
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  disabled?: boolean;
}) {
  const [show, setShow] = useState(false);
  const tail = value.slice(-4);
  return (
    <div className="flex-1 space-y-1">
      <div className="relative">
        <Input
          value={value}
          onChange={onChange}
          type={show ? 'text' : 'password'}
          placeholder="Paste SimpleFIN setup token…"
          className="font-mono text-xs pr-8"
          disabled={disabled}
          autoComplete="off"
        />
        {value && (
          <button
            type="button"
            tabIndex={-1}
            onClick={() => setShow(v => !v)}
            className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
          >
            {show ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
          </button>
        )}
      </div>
      {value && !show && (
        <p className="text-[11px] text-muted-foreground font-mono pl-0.5 select-none">
          ···{tail}
        </p>
      )}
    </div>
  );
}

function fmtDate(iso: string | null | undefined): string {
  const d = parseUtc(iso);
  if (!d) return '—';
  return d.toLocaleString(undefined, {
    month: 'short', day: 'numeric', year: 'numeric',
    hour: '2-digit', minute: '2-digit',
  });
}

function fmtShortDate(date: string | null | undefined): string {
  if (!date) return '—';
  const d = new Date(`${date}T00:00:00`);
  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
}

function fmtDollars(cents: Cents): string {
  return formatCentsUSD(cents, { dash: true });
}

function MatchBadge({ status, billName }: { status: string; billName?: string | null }) {
  if (status === 'matched') {
    return (
      <span className="text-[10px] font-medium text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 px-1.5 py-0.5 rounded-full max-w-[120px] truncate inline-block" title={billName || 'matched'}>
        {billName || 'matched'}
      </span>
    );
  }
  if (status === 'ignored') {
    return <span className="text-[10px] font-medium text-amber-600 dark:text-amber-400 bg-amber-500/10 px-1.5 py-0.5 rounded-full">ignored</span>;
  }
  return <span className="text-[10px] font-medium text-muted-foreground bg-muted/60 px-1.5 py-0.5 rounded-full">unmatched</span>;
}

function BillPickerDialog({ open, onClose, transaction, bills, onConfirm, busy }: {
  open: boolean;
  onClose: () => void;
  transaction?: BankTx | null;
  bills: Bill[];
  onConfirm: (billId: number | string | null) => void;
  busy: boolean;
}) {
  const [search, setSearch] = useState('');
  const [selectedId, setSelectedId] = useState<number | string | null>(null);

  useEffect(() => { if (open) { setSearch(''); setSelectedId(null); } }, [open]);

  const filtered = useMemo(() => {
    const q = search.toLowerCase();
    return (bills || []).filter(b => !q || b.name.toLowerCase().includes(q));
  }, [bills, search]);

  const txDate  = transaction?.posted_date || transaction?.transacted_at?.slice(0, 10);
  const txLabel = transaction?.payee || transaction?.description || '—';
  const txAmt   = transaction ? fmtDollars(transaction.amount) : '';

  return (
    <Dialog open={open} onOpenChange={v => { if (!v) onClose(); }}>
      <DialogContent className="max-w-md">
        <DialogHeader>
          <DialogTitle>Match to bill</DialogTitle>
          <p className="text-sm text-muted-foreground mt-1">
            <span className="font-medium text-foreground">{txLabel}</span>
            {txDate && <span className="ml-2 text-xs">{fmtShortDate(txDate)}</span>}
            <span className="ml-2 font-medium text-destructive/80">{txAmt}</span>
          </p>
          <p className="text-xs text-muted-foreground">
            A payment record will be created for the selected bill using this transaction's amount and date.
          </p>
        </DialogHeader>

        <Input
          autoFocus
          placeholder="Search bills…"
          value={search}
          onChange={e => setSearch(e.target.value)}
          className="text-sm"
        />

        <div className="max-h-56 overflow-y-auto rounded-lg border border-border/60 divide-y divide-border/40">
          {filtered.length === 0 ? (
            <p className="px-3 py-4 text-sm text-muted-foreground text-center">No bills found.</p>
          ) : filtered.map(bill => (
            <button
              key={bill.id}
              type="button"
              onClick={() => setSelectedId(bill.id)}
              className={cn(
                'w-full flex items-center justify-between px-3 py-2.5 text-left text-sm transition-colors hover:bg-muted/40',
                selectedId === bill.id && 'bg-primary/10 text-primary',
              )}
            >
              <span className="truncate font-medium">{bill.name}</span>
              <span className="shrink-0 ml-3 text-xs text-muted-foreground tabular-nums">
                ${(bill.expected_amount ?? 0).toFixed(2)}/mo
              </span>
            </button>
          ))}
        </div>

        <DialogFooter>
          <Button variant="ghost" onClick={onClose} disabled={busy}>Cancel</Button>
          <Button onClick={() => onConfirm(selectedId)} disabled={!selectedId || busy}>
            {busy ? <><Loader2 className="h-3.5 w-3.5 animate-spin mr-1.5" />Matching…</> : 'Confirm match'}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

interface AccountRowProps {
  account: BankAccount;
  expanded: boolean;
  onToggleExpand: () => void;
  onToggleMonitored: (accountId: number | string, monitored: boolean) => void;
  toggling: boolean;
  onMatch: (tx: BankTx) => void;
  onUnmatch: (tx: BankTx) => void;
  matchingTxId: number | string | null;
}

function AccountRow({ account, expanded, onToggleExpand, onToggleMonitored, toggling, onMatch, onUnmatch, matchingTxId }: AccountRowProps) {
  const txDate = account.transactions?.[0]?.posted_date || account.transactions?.[0]?.transacted_at?.slice(0, 10);

  return (
    <div className="border-t border-border/40 first:border-t-0">
      <div
        className={cn('flex items-center gap-3 px-4 py-2.5', account.monitored && 'hover:bg-muted/20 cursor-pointer')}
        onClick={account.monitored ? onToggleExpand : undefined}
      >
        <span className="text-muted-foreground shrink-0 w-3.5" onClick={e => e.stopPropagation()}>
          {account.monitored && (expanded
            ? <ChevronDown className="h-3.5 w-3.5" />
            : <ChevronRight className="h-3.5 w-3.5" />)}
        </span>

        {/* Monitored toggle */}
        <div onClick={e => e.stopPropagation()}>
          <Switch
            checked={!!account.monitored}
            onCheckedChange={v => onToggleMonitored(account.id, v)}
            disabled={toggling}
            aria-label={`Monitor ${account.name}`}
          />
        </div>

        {/* Account name */}
        <div className="flex-1 min-w-0">
          <p className={cn('text-sm font-medium truncate', !account.monitored && 'text-muted-foreground')}>
            {account.name}
          </p>
          {account.org_name && (
            <p className="text-xs text-muted-foreground truncate">{account.org_name}</p>
          )}
        </div>

        {/* Balance */}
        <div className="text-right shrink-0 hidden sm:block">
          <p className={cn('text-xs font-medium tabular-nums', !account.monitored && 'text-muted-foreground')}>
            {fmtDollars(account.balance)}
          </p>
          {txDate && (
            <p className="text-[10px] text-muted-foreground">last tx {fmtShortDate(txDate)}</p>
          )}
        </div>

        {/* Tx count */}
        <div className="shrink-0 text-right">
          <p className="text-xs text-muted-foreground tabular-nums">
            {account.transaction_count} tx
          </p>
          {!account.monitored && (
            <p className="text-[10px] text-muted-foreground/60">skipped</p>
          )}
        </div>
      </div>

      {expanded && account.monitored && (
        <div className="border-t border-border/30 bg-muted/10">
          {account.transactions.length === 0 ? (
            <p className="px-6 py-3 text-xs text-muted-foreground italic">No transactions synced for this account.</p>
          ) : (
            <div className="overflow-x-auto">
              <table className="w-full text-xs">
                <thead>
                  <tr className="border-b border-border/30">
                    <th className="px-4 py-2 text-left font-medium text-muted-foreground whitespace-nowrap">Date</th>
                    <th className="px-4 py-2 text-left font-medium text-muted-foreground">Payee / Description</th>
                    <th className="px-4 py-2 text-right font-medium text-muted-foreground whitespace-nowrap">Amount</th>
                    <th className="px-4 py-2 text-right font-medium text-muted-foreground whitespace-nowrap">Bill</th>
                  </tr>
                </thead>
                <tbody>
                  {account.transactions.map(tx => (
                    <tr key={tx.id} className="border-b border-border/20 last:border-b-0 hover:bg-muted/20">
                      <td className="px-4 py-1.5 text-muted-foreground whitespace-nowrap">
                        {fmtShortDate(tx.posted_date || tx.transacted_at?.slice(0, 10))}
                      </td>
                      <td className="px-4 py-1.5 max-w-xs">
                        <p className="truncate font-medium">{tx.payee || tx.description || '—'}</p>
                        {tx.payee && tx.description && tx.payee !== tx.description && (
                          <p className="truncate text-muted-foreground">{tx.description}</p>
                        )}
                      </td>
                      <td className={cn('px-4 py-1.5 text-right tabular-nums whitespace-nowrap font-medium', tx.amount < 0 ? 'text-destructive/80' : 'text-emerald-600 dark:text-emerald-400')}>
                        {fmtDollars(tx.amount)}
                      </td>
                      <td className="px-4 py-1.5 text-right whitespace-nowrap">
                        {tx.match_status === 'matched' ? (
                          <div className="flex items-center justify-end gap-1.5">
                            <MatchBadge status="matched" billName={tx.matched_bill_name} />
                            <button
                              type="button"
                              title="Remove match"
                              disabled={matchingTxId === tx.id}
                              onClick={() => onUnmatch(tx)}
                              className="text-muted-foreground hover:text-destructive disabled:opacity-40 transition-colors"
                            >
                              {matchingTxId === tx.id
                                ? <Loader2 className="h-3 w-3 animate-spin" />
                                : <Unlink className="h-3 w-3" />}
                            </button>
                          </div>
                        ) : tx.match_status === 'ignored' ? (
                          <MatchBadge status="ignored" />
                        ) : (
                          <button
                            type="button"
                            disabled={matchingTxId === tx.id}
                            onClick={() => onMatch(tx)}
                            className="text-[10px] font-medium text-primary/70 hover:text-primary bg-primary/5 hover:bg-primary/10 px-1.5 py-0.5 rounded-full transition-colors disabled:opacity-40"
                          >
                            {matchingTxId === tx.id ? <Loader2 className="h-3 w-3 animate-spin inline" /> : '+ match'}
                          </button>
                        )}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

export default function BankSyncSection({ onConnectionChange, cardProps = {} }: {
  onConnectionChange?: (conn: Connection | null) => void;
  cardProps?: Partial<SectionCardProps>;
}) {
  const [enabled, setEnabled]         = useState<boolean | null>(null);
  const [syncDays, setSyncDays]       = useState(30);
  const [seedDays, setSeedDays]       = useState(44);
  const [serverTz, setServerTz]       = useState<string | null>(null);
  const [connections, setConnections] = useState<Connection[]>([]);
  const [accountsBySource, setAccountsBySource]   = useState<Record<string, BankAccount[]>>({});
  const [accountsLoading, setAccountsLoading]     = useState<Record<string, boolean>>({});
  const [accountsErrorBySource, setAccountsError] = useState<Record<string, string>>({});
  const [loadError, setLoadError]     = useState('');
  const [setupToken, setSetupToken]   = useState('');
  const [connecting, setConnecting]   = useState(false);
  const [syncing, setSyncing]         = useState<number | string | null>(null);
  const [backfilling, setBackfilling] = useState<number | string | null>(null);
  const [disconnectTarget, setDisconnectTarget] = useState<Connection | null>(null);
  const [disconnecting, setDisconnecting]       = useState(false);
  const [expandedAccount, setExpandedAccount]   = useState<number | string | null>(null);
  const [togglingAccount, setTogglingAccount]   = useState<number | string | null>(null);
  const [bills, setBills]                       = useState<Bill[]>([]);
  const [matchTarget, setMatchTarget]           = useState<{ sourceId: number | string; tx: BankTx } | null>(null);
  const [matchingTxId, setMatchingTxId]         = useState<number | string | null>(null);
  const [autoMatchRefreshKey, setAutoMatchRefreshKey] = useState(0);

  // Bank tracking state
  const [btEnabled, setBtEnabled]         = useState(false);
  const [btAccountId, setBtAccountId]     = useState('');
  const [btPendingDays,     setBtPendingDays]     = useState(3);
  const [btLateGraceDays, setBtLateGraceDays] = useState(0);
  const [btAccounts, setBtAccounts]       = useState<FinAccount[]>([]);
  const [btSaving, setBtSaving]           = useState(false);

  // Auto-categorization state
  const [acmEnabled, setAcmEnabled] = useState(true);
  const [acmSaving, setAcmSaving]   = useState(false);

  const loadAccounts = useCallback(async (conns: Connection[]) => {
    for (const conn of conns) {
      setAccountsLoading(prev => ({ ...prev, [conn.id]: true }));
      setAccountsError(prev => ({ ...prev, [conn.id]: '' }));
      try {
        const accounts = await api.dataSourceAccounts(conn.id) as BankAccount[];
        setAccountsBySource(prev => ({ ...prev, [conn.id]: accounts }));
      } catch (err) {
        setAccountsError(prev => ({ ...prev, [conn.id]: errMessage(err, 'Failed to load accounts') }));
      } finally {
        setAccountsLoading(prev => ({ ...prev, [conn.id]: false }));
      }
    }
  }, []);

  // Load bank tracking settings and available accounts
  const loadBankTracking = useCallback(async () => {
    try {
      const [settings, accounts] = await Promise.all([
        api.settings() as Promise<Record<string, string>>,
        api.allFinancialAccounts().catch(() => []),
      ]);
      setBtEnabled(settings.bank_tracking_enabled === 'true');
      setBtAccountId(settings.bank_tracking_account_id || '');
      setBtPendingDays(parseInt(settings.bank_tracking_pending_days || '', 10) || 3);
      setBtLateGraceDays(parseInt(settings.bank_late_attribution_days || '', 10) || 0);
      setBtAccounts(Array.isArray(accounts) ? accounts as FinAccount[] : []);
      setAcmEnabled(settings.bank_auto_categorize_merchants !== 'false');
    } catch {
      // non-fatal — bank tracking section just won't populate
    }
  }, []);

  const handleBtSave = useCallback(async (patch: BtPatch) => {
    setBtSaving(true);
    try {
      const next = {
        bank_tracking_enabled:    String(patch.enabled  ?? btEnabled),
        bank_tracking_account_id: String(patch.accountId ?? btAccountId),
        bank_tracking_pending_days:   String(patch.pendingDays   ?? btPendingDays),
        bank_late_attribution_days:   String(patch.lateGraceDays ?? btLateGraceDays),
      };
      await api.saveSettings(next);
      if (patch.enabled !== undefined) setBtEnabled(patch.enabled);
      if (patch.accountId !== undefined) setBtAccountId(patch.accountId);
      if (patch.pendingDays   !== undefined) setBtPendingDays(patch.pendingDays);
      if (patch.lateGraceDays !== undefined) setBtLateGraceDays(patch.lateGraceDays);
      toast.success('Bank tracking settings saved');
    } catch (err) {
      toast.error(errMessage(err, 'Failed to save bank tracking settings'));
    } finally {
      setBtSaving(false);
    }
  }, [btEnabled, btAccountId, btPendingDays, btLateGraceDays]);

  const handleAcmSave = useCallback(async (next: boolean) => {
    setAcmSaving(true);
    try {
      await api.saveSettings({ bank_auto_categorize_merchants: String(next) });
      setAcmEnabled(next);
      toast.success('Auto-categorization setting saved');
    } catch (err) {
      toast.error(errMessage(err, 'Failed to save auto-categorization setting'));
    } finally {
      setAcmSaving(false);
    }
  }, []);

  const load = useCallback(async () => {
    setLoadError('');
    try {
      const [status, sources] = await Promise.all([
        api.simplefinStatus() as Promise<{ enabled?: boolean; sync_days?: number; seed_days?: number; timezone?: string | null }>,
        api.dataSources({ type: 'provider_sync' }),
      ]);
      setEnabled(!!status.enabled);
      setSyncDays(status.sync_days ?? 30);
      setSeedDays(status.seed_days ?? 44);
      setServerTz(status.timezone || null);
      const conns = Array.isArray(sources) ? (sources as Connection[]).filter(s => s.provider === 'simplefin') : [];
      setConnections(conns);
      onConnectionChange?.(conns[0] || null);
      if (conns.length > 0) loadAccounts(conns);
    } catch (err) {
      setEnabled(false);
      setLoadError(errMessage(err, 'Failed to load bank sync status'));
    }
  }, [onConnectionChange, loadAccounts]);

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

  // Load bills once when connections become available (for the match picker)
  useEffect(() => {
    if (connections.length > 0 && bills.length === 0) {
      api.allBills().then(b => setBills(Array.isArray(b) ? b : [])).catch(err => console.error('[BankSyncSection] failed to load bills', err));
    }
  }, [connections, bills.length]);

  function updateTxInState(sourceId: number | string, txId: number | string, updates: Partial<BankTx>) {
    setAccountsBySource(prev => ({
      ...prev,
      [sourceId]: (prev[sourceId] || []).map(acc => ({
        ...acc,
        transactions: acc.transactions.map(tx => tx.id === txId ? { ...tx, ...updates } : tx),
      })),
    }));
  }

  const handleMatch = (sourceId: number | string, tx: BankTx) => setMatchTarget({ sourceId, tx });

  const handleConfirmMatch = async (billId: number | string | null) => {
    if (!matchTarget || !billId) return;
    const { sourceId, tx } = matchTarget;
    setMatchingTxId(tx.id);
    try {
      const { transaction } = await api.confirmTransactionMatch(tx.id, billId) as { transaction: BankTx };
      updateTxInState(sourceId, tx.id, {
        match_status: transaction.match_status,
        matched_bill_id: transaction.matched_bill_id,
        matched_bill_name: transaction.matched_bill_name,
      });
      setMatchTarget(null);
      toast.success(`Matched to "${transaction.matched_bill_name}" — payment recorded for ${fmtShortDate(tx.posted_date || tx.transacted_at?.slice(0, 10))}.`);
    } catch (err) {
      toast.error(errMessage(err, 'Failed to match transaction.'));
    } finally {
      setMatchingTxId(null);
    }
  };

  const handleUnmatch = async (sourceId: number | string, tx: BankTx) => {
    setMatchingTxId(tx.id);
    try {
      await api.unmatchTransaction(tx.id);
      updateTxInState(sourceId, tx.id, { match_status: 'unmatched', matched_bill_id: null, matched_bill_name: null });
      toast.success('Match removed.');
    } catch (err) {
      toast.error(errMessage(err, 'Failed to remove match.'));
    } finally {
      setMatchingTxId(null);
    }
  };

  const handleConnect = async () => {
    const token = setupToken.trim();
    if (!token) { toast.error('Paste your SimpleFIN setup token first.'); return; }
    setConnecting(true);
    try {
      const result = await api.connectSimplefin(token) as { accountsUpserted?: number; transactionsNew?: number };
      toast.success(`Connected — ${result.accountsUpserted} account(s), ${result.transactionsNew} new transaction(s).`);
      setSetupToken('');
      await load();
    } catch (err) {
      toast.error(errMessage(err, 'Failed to connect SimpleFIN'));
    } finally {
      setConnecting(false);
    }
  };

  const handleSync = async (id: number | string) => {
    setSyncing(id);
    try {
      const result = await api.syncDataSource(id) as { errlist?: string; transactionsNew?: number };
      if (result.errlist) {
        toast.warning(`Synced ${result.transactionsNew} new transaction(s), but some connections need attention: ${result.errlist}`);
      } else {
        toast.success(`Synced — ${result.transactionsNew} new transaction(s).`);
      }
      setAutoMatchRefreshKey(k => k + 1);
      await load();
    } catch (err) {
      toast.error(errMessage(err, 'Sync failed'));
      await load();
    } finally {
      setSyncing(null);
    }
  };

  const handleBackfill = async (id: number | string) => {
    setBackfilling(id);
    try {
      const result = await api.backfillDataSource(id) as { errlist?: string; transactionsNew?: number };
      if (result.errlist) {
        toast.warning(`Backfill complete — ${result.transactionsNew} new transaction(s). Some connections need attention: ${result.errlist}`);
      } else {
        toast.success(`Backfill complete — ${result.transactionsNew} new transaction(s) pulled from the last ${seedDays} days.`);
      }
      setAutoMatchRefreshKey(k => k + 1);
      await load();
    } catch (err) {
      toast.error(errMessage(err, 'Backfill failed'));
      await load();
    } finally {
      setBackfilling(null);
    }
  };

  const handleDisconnect = async () => {
    if (!disconnectTarget) return;
    setDisconnecting(true);
    try {
      await api.deleteDataSource(disconnectTarget.id);
      toast.success('SimpleFIN disconnected.');
      setDisconnectTarget(null);
      await load();
    } catch (err) {
      toast.error(errMessage(err, 'Failed to disconnect'));
    } finally {
      setDisconnecting(false);
    }
  };

  const handleToggleMonitored = async (sourceId: number | string, accountId: number | string, monitored: boolean) => {
    setTogglingAccount(accountId);
    // Optimistic update
    setAccountsBySource(prev => ({
      ...prev,
      [sourceId]: (prev[sourceId] || []).map(a =>
        a.id === accountId ? { ...a, monitored } : a
      ),
    }));
    try {
      await api.setAccountMonitored(sourceId, accountId, monitored);
    } catch (err) {
      // Revert on failure
      setAccountsBySource(prev => ({
        ...prev,
        [sourceId]: (prev[sourceId] || []).map(a =>
          a.id === accountId ? { ...a, monitored: !monitored } : a
        ),
      }));
      toast.error(errMessage(err, 'Failed to update account'));
    } finally {
      setTogglingAccount(null);
    }
  };

  function connWarning(conn: Connection): { kind: string; label: string } | null {
    if (!conn.last_error) return null;
    if (conn.status === 'error') return { kind: 'error', label: 'Sync error' };
    // Partial errlist: sync succeeded but some bank connections need attention
    return { kind: 'partial', label: 'Some connections need attention' };
  }

  if (enabled === null) {
    return (
      <SectionCard title="Bank Sync" {...cardProps}>
        <div className="px-6 py-6 flex items-center gap-2 text-sm text-muted-foreground">
          <Loader2 className="h-4 w-4 animate-spin" />
          Loading…
        </div>
      </SectionCard>
    );
  }

  return (
    <>
      <SectionCard title="Bank Sync" subtitle="Connect your SimpleFIN Bridge to sync read-only bank transactions." {...cardProps}>
        {!enabled ? (
          <div className="px-6 py-5 text-sm text-muted-foreground">
            Bank sync is not enabled on this server. Ask your administrator to enable it in the Admin panel.
          </div>
        ) : (
          <>
            {loadError && (
              <div className="px-6 py-3 text-sm text-destructive">{loadError}</div>
            )}

            {connections.length > 0 && connections.map(conn => {
              const accounts = accountsBySource[conn.id] || [];
              const accsLoading = accountsLoading[conn.id];
              const accsError = accountsErrorBySource[conn.id];
              const monitoredCount = accounts.filter(a => a.monitored).length;

              const warning = connWarning(conn);
              return (
                <div key={conn.id} className="px-6 py-4 space-y-3">
                  {warning && (
                    <div className="flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 py-2.5 text-sm text-amber-700 dark:text-amber-400">
                      <AlertTriangle className="h-4 w-4 shrink-0 mt-0.5" />
                      <div className="flex-1 min-w-0">
                        <span className="font-medium">{warning.label}</span>
                        <span className="ml-1 font-normal opacity-90">— {conn.last_error}</span>
                        {warning.kind === 'partial' && (
                          <span className="block text-xs mt-0.5 opacity-75">
                            Re-authenticate the affected institutions in your SimpleFIN Bridge account to restore full sync.
                          </span>
                        )}
                        {warning.kind === 'error' && conn.last_sync_at && (
                          <span className="block text-xs mt-0.5 opacity-75">
                            Last successful sync: {fmtDate(conn.last_sync_at)}
                          </span>
                        )}
                      </div>
                      <button
                        type="button"
                        onClick={() => handleSync(conn.id)}
                        disabled={syncing === conn.id}
                        className="shrink-0 text-xs font-medium underline-offset-2 hover:underline disabled:opacity-50"
                      >
                        {syncing === conn.id ? 'Syncing…' : 'Retry'}
                      </button>
                    </div>
                  )}

                  {/* Header row */}
                  <div className="flex items-start justify-between gap-3">
                    <div className="flex items-center gap-3 min-w-0">
                      <Building2 className="h-5 w-5 shrink-0 text-muted-foreground" />
                      <div className="min-w-0">
                        <p className="text-sm font-medium truncate">{conn.name}</p>
                        <p className="text-xs text-muted-foreground mt-0.5">
                          {conn.account_count} account{conn.account_count !== 1 ? 's' : ''} ·{' '}
                          {conn.transaction_count} transaction{conn.transaction_count !== 1 ? 's' : ''}
                          {accounts.length > 0 && ` · ${monitoredCount} monitored`}
                        </p>
                      </div>
                    </div>
                    <div className="flex items-center gap-2 shrink-0">
                      <Button
                        size="sm" variant="outline"
                        onClick={() => handleSync(conn.id)}
                        disabled={syncing === conn.id || backfilling === conn.id}
                        className="h-8 text-xs gap-1.5"
                      >
                        {syncing === conn.id
                          ? <><Loader2 className="h-3.5 w-3.5 animate-spin" />Syncing…</>
                          : <><RefreshCw className="h-3.5 w-3.5" />Sync Now</>}
                      </Button>
                      <Button
                        size="sm" variant="outline"
                        onClick={() => handleBackfill(conn.id)}
                        disabled={syncing === conn.id || backfilling === conn.id}
                        className="h-8 text-xs gap-1.5 text-muted-foreground"
                        title={`Pull up to ${seedDays} days of transaction history`}
                      >
                        {backfilling === conn.id
                          ? <><Loader2 className="h-3.5 w-3.5 animate-spin" />Backfilling…</>
                          : <><History className="h-3.5 w-3.5" />{seedDays}d Backfill</>}
                      </Button>
                      <Button
                        size="sm" variant="ghost"
                        onClick={() => setDisconnectTarget(conn)}
                        className="h-8 text-xs gap-1.5 text-muted-foreground hover:text-destructive"
                      >
                        <Link2Off className="h-3.5 w-3.5" />
                        Disconnect
                      </Button>
                    </div>
                  </div>

                  {/* Sync status grid */}
                  <div className="grid grid-cols-2 gap-2 sm:grid-cols-4 text-xs">
                    <div className="rounded-lg border border-border/60 bg-muted/20 px-3 py-2">
                      <p className="text-muted-foreground">Last sync</p>
                      <p className="font-medium mt-0.5">{fmtDate(conn.last_sync_at)}</p>
                    </div>
                    <div className={cn(
                      'rounded-lg border px-3 py-2',
                      conn.last_error ? 'border-destructive/30 bg-destructive/5' : 'border-border/60 bg-muted/20',
                    )}>
                      <p className="text-muted-foreground">Status</p>
                      <p className={cn('font-medium mt-0.5', conn.last_error ? 'text-destructive' : '')}>
                        {conn.last_error ? conn.last_error : (conn.status === 'active' ? 'Active' : conn.status)}
                      </p>
                    </div>
                    <div className="rounded-lg border border-border/60 bg-muted/20 px-3 py-2">
                      <p className="text-muted-foreground">History window</p>
                      <p className="font-medium mt-0.5">{syncDays} days</p>
                    </div>
                    <div className="rounded-lg border border-border/60 bg-muted/20 px-3 py-2">
                      <p className="text-muted-foreground">Server timezone</p>
                      <p className="font-medium mt-0.5 truncate" title={serverTz || ''}>{serverTz || '—'}</p>
                    </div>
                  </div>

                  {/* Accounts section */}
                  <div className="rounded-lg border border-border/60 overflow-hidden">
                    <div className="px-4 py-2 bg-muted/20 border-b border-border/40 flex items-center justify-between">
                      <p className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
                        Accounts
                      </p>
                      <p className="text-[10px] text-muted-foreground">
                        Toggle to include / exclude from bill matching
                      </p>
                    </div>

                    {accsLoading ? (
                      <div className="flex items-center gap-2 px-4 py-3 text-xs text-muted-foreground">
                        <Loader2 className="h-3.5 w-3.5 animate-spin" />
                        Loading accounts…
                      </div>
                    ) : accsError ? (
                      <p className="px-4 py-3 text-xs text-destructive">{accsError}</p>
                    ) : accounts.length === 0 ? (
                      <p className="px-4 py-3 text-xs text-muted-foreground italic">No accounts found.</p>
                    ) : (
                      accounts.map(account => (
                        <AccountRow
                          key={account.id}
                          account={account}
                          expanded={expandedAccount === account.id}
                          onToggleExpand={() => setExpandedAccount(prev => prev === account.id ? null : account.id)}
                          onToggleMonitored={(accountId, monitored) => handleToggleMonitored(conn.id, accountId, monitored)}
                          toggling={togglingAccount === account.id}
                          onMatch={tx => handleMatch(conn.id, tx)}
                          onUnmatch={tx => handleUnmatch(conn.id, tx)}
                          matchingTxId={matchingTxId}
                        />
                      ))
                    )}
                  </div>
                </div>
              );
            })}

            {connections.length === 0 && (
              <div className="px-6 py-5 space-y-4">
                <div className="rounded-lg border border-border/60 bg-muted/20 p-4 text-sm text-muted-foreground">
                  <p className="font-medium text-foreground mb-1">Connect a SimpleFIN Bridge account</p>
                  <p>Paste your SimpleFIN setup token below. BillTracker only stores an encrypted access URL — no bank credentials are saved.</p>
                  <p className="mt-2">
                    Need a token?{' '}
                    <a
                      href="https://beta-bridge.simplefin.org/"
                      target="_blank"
                      rel="noreferrer"
                      className="inline-flex items-center gap-1 font-medium text-primary underline-offset-4 hover:underline"
                    >
                      Open SimpleFIN Bridge
                      <ExternalLink className="h-3 w-3" />
                    </a>
                  </p>
                </div>
                <div className="flex gap-2">
                  <TokenInput
                    value={setupToken}
                    onChange={e => setSetupToken(e.target.value)}
                    disabled={connecting}
                  />
                  <Button onClick={handleConnect} disabled={connecting || !setupToken.trim()} className="shrink-0">
                    {connecting ? <><Loader2 className="h-4 w-4 animate-spin mr-1.5" />Connecting…</> : 'Connect'}
                  </Button>
                </div>
              </div>
            )}

            {connections.length > 0 && (
              <div className="px-6 py-4 border-t border-border/50 space-y-2">
                <div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
                  <p className="text-xs font-medium text-muted-foreground">Add another connection</p>
                  <a
                    href="https://beta-bridge.simplefin.org/"
                    target="_blank"
                    rel="noreferrer"
                    className="inline-flex items-center gap-1 text-xs font-medium text-primary underline-offset-4 hover:underline"
                  >
                    Get a SimpleFIN token
                    <ExternalLink className="h-3 w-3" />
                  </a>
                </div>
                <div className="flex gap-2">
                  <TokenInput
                    value={setupToken}
                    onChange={e => setSetupToken(e.target.value)}
                    disabled={connecting}
                  />
                  <Button size="sm" onClick={handleConnect} disabled={connecting || !setupToken.trim()} className="shrink-0">
                    {connecting ? <><Loader2 className="h-3.5 w-3.5 animate-spin mr-1" />Connecting…</> : 'Connect'}
                  </Button>
                </div>
              </div>
            )}
          </>
        )}
      </SectionCard>

      {/* ── Auto-match review panel ── */}
      {enabled && connections.length > 0 && (
        <AutoMatchReview refreshKey={autoMatchRefreshKey} />
      )}

      {/* ── Bank Budget Tracking ── */}
      {enabled && connections.length > 0 && (
        <SectionCard
          title="Bank Budget Tracking"
          subtitle="Use your live bank balance as the starting point for your monthly budget instead of manually-entered amounts."
          {...cardProps}
        >
          <div className="px-6 py-5 space-y-5">

            {/* Toggle */}
            <div className="flex items-center justify-between gap-4">
              <div className="space-y-0.5">
                <Label htmlFor="bt-toggle" className="text-sm font-medium">
                  Use bank balance for budget
                </Label>
                <p className="text-xs text-muted-foreground">
                  Replaces manual starting amounts. Remaining&nbsp;= bank balance − pending payments − unpaid bills.
                </p>
              </div>
              <Switch
                id="bt-toggle"
                checked={btEnabled}
                disabled={btSaving}
                onCheckedChange={v => handleBtSave({ enabled: v })}
              />
            </div>

            {btEnabled && (
              <>
                {/* Account picker */}
                <div className="space-y-1.5">
                  <Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                    Tracking account
                  </Label>
                  {btAccounts.length === 0 ? (
                    <p className="text-xs text-muted-foreground">No bank accounts found. Sync your SimpleFIN connection first.</p>
                  ) : (
                    <Select
                      value={btAccountId}
                      onValueChange={v => handleBtSave({ accountId: v })}
                      disabled={btSaving}
                    >
                      <SelectTrigger className="h-9 text-sm">
                        <SelectValue placeholder="Select a checking account…" />
                      </SelectTrigger>
                      <SelectContent>
                        {btAccounts.map(acc => (
                          <SelectItem key={acc.id} value={String(acc.id)}>
                            <div className="flex items-center gap-2">
                              <Landmark className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
                              <span>{acc.org_name ? `${acc.org_name} — ` : ''}{acc.name}</span>
                              {acc.balance_dollars != null && (
                                <span className="ml-auto text-xs text-muted-foreground tabular-nums">
                                  {formatUSD(acc.balance_dollars)}
                                </span>
                              )}
                            </div>
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  )}
                </div>

                {/* Pending window */}
                <div className="space-y-1.5">
                  <Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                    Pending payment window
                  </Label>
                  <p className="text-xs text-muted-foreground">
                    Payments you mark as paid within this many days are shown as <em>pending</em> —
                    the money may not have cleared your bank yet, so they're subtracted from your effective balance.
                  </p>
                  <Select
                    value={String(btPendingDays)}
                    onValueChange={v => handleBtSave({ pendingDays: parseInt(v, 10) })}
                    disabled={btSaving}
                  >
                    <SelectTrigger className="h-9 w-40 text-sm">
                      <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value="0">No pending (instant)</SelectItem>
                      <SelectItem value="1">1 day</SelectItem>
                      <SelectItem value="2">2 days</SelectItem>
                      <SelectItem value="3">3 days (recommended)</SelectItem>
                      <SelectItem value="5">5 days</SelectItem>
                      <SelectItem value="7">7 days</SelectItem>
                    </SelectContent>
                  </Select>
                </div>

                {/* Late payment grace window */}
                <div className="space-y-1.5">
                  <Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                    Late payment grace window
                  </Label>
                  <p className="text-xs text-muted-foreground">
                    If a payment posts in the first N days of a new month but the bill was due in the prior month,
                    automatically count it for the prior month — no prompt needed.
                  </p>
                  <Select
                    value={String(btLateGraceDays)}
                    onValueChange={v => handleBtSave({ lateGraceDays: parseInt(v, 10) })}
                    disabled={btSaving}
                  >
                    <SelectTrigger className="h-9 w-48 text-sm">
                      <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value="0">Off — prompt me each time</SelectItem>
                      <SelectItem value="1">1 day</SelectItem>
                      <SelectItem value="2">2 days</SelectItem>
                      <SelectItem value="3">3 days (recommended)</SelectItem>
                      <SelectItem value="5">5 days</SelectItem>
                    </SelectContent>
                  </Select>
                  {btLateGraceDays > 0 && (
                    <p className="text-[11px] text-emerald-600 dark:text-emerald-400">
                      Any payment posting on the 1st–{btLateGraceDays}{btLateGraceDays === 1 ? 'st' : btLateGraceDays === 2 ? 'nd' : btLateGraceDays === 3 ? 'rd' : 'th'} will automatically count for the prior month if the bill was due then.
                    </p>
                  )}
                </div>

                {/* Info callout */}
                <div className="rounded-lg border border-border/60 bg-muted/30 px-4 py-3 text-xs text-muted-foreground space-y-1">
                  <p><span className="font-semibold text-foreground">How it works:</span> Your live bank balance is fetched every time your data syncs. Bills you've already marked paid are not double-counted — your bank balance reflects them. Only unpaid bills still due this month are subtracted.</p>
                  <p>Bills marked paid within the pending window show a <span className="font-semibold">Pending</span> badge in the tracker, since the bank may not have processed them yet.</p>
                </div>
              </>
            )}
          </div>
        </SectionCard>
      )}

      {enabled && connections.length > 0 && (
        <SectionCard
          title="Auto-categorize Transactions"
          subtitle="Match merchant names and categories from a 5,000-entry reference list during sync."
          {...cardProps}
        >
          <div className="px-6 py-5">
            <div className="flex items-center justify-between gap-4">
              <div className="space-y-0.5">
                <Label htmlFor="acm-toggle" className="text-sm font-medium">
                  Auto-categorize transactions
                </Label>
                <p className="text-xs text-muted-foreground">
                  When new transactions sync, recognized merchants and stores are automatically assigned a spending category.
                </p>
              </div>
              <Switch
                id="acm-toggle"
                checked={acmEnabled}
                disabled={acmSaving}
                onCheckedChange={handleAcmSave}
              />
            </div>
          </div>
        </SectionCard>
      )}

      <AlertDialog open={!!disconnectTarget} onOpenChange={open => { if (!open) setDisconnectTarget(null); }}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Disconnect SimpleFIN?</AlertDialogTitle>
            <AlertDialogDescription>
              This removes the connection and deletes synced accounts. Previously synced transactions
              are kept but will no longer be associated with a data source.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel disabled={disconnecting}>Cancel</AlertDialogCancel>
            <AlertDialogAction onClick={handleDisconnect} disabled={disconnecting}
              className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
              {disconnecting ? 'Disconnecting…' : 'Disconnect'}
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      <BillPickerDialog
        open={!!matchTarget}
        onClose={() => setMatchTarget(null)}
        transaction={matchTarget?.tx}
        bills={bills}
        onConfirm={handleConfirmMatch}
        busy={!!matchingTxId}
      />
    </>
  );
}
