"use client";

import { useEffect, useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { ALL_ORGANIZATIONS } from "@/lib/sag/entities";
import { useLocalStorage } from "@/lib/hooks/use-local-storage";
import {
  BILL_CATEGORIES,
  type Bill,
  type BillCategory,
} from "@/lib/finance/bill-types";
import { formatCurrency } from "@/lib/utils";

interface PlaidAccount {
  accountId: string;
  itemId: string;
  accessToken?: string;
  name: string;
  mask?: string;
  entitySlug?: string;
}

interface GmailCandidate {
  accountId: string;
  accountEmail: string;
  threadId: string;
  messageId: string;
  subject: string;
  fromEmail: string;
  fromName: string;
  snippet: string;
  internalDateMs: number;
}

interface PlaidSuggestion {
  vendor: string;
  amount: number;
  category: string | null;
  recurrencePattern: "weekly" | "biweekly" | "semimonthly" | "monthly" | "annual" | "unknown";
  nextDueDate: string;
  lastDate: string;
  status: string;
  plaidStreamId: string;
  plaidAccountId: string;
}

interface ParsedGmail {
  vendor?: string;
  amount?: number;
  dueDate?: string | null;
  accountNumber?: string | null;
  category?: string;
  isBill?: boolean;
  alreadyPaid?: boolean;
  confidence?: string;
  notes?: string;
}

interface BillSuggestionsProps {
  onAddBills: (bills: Array<Omit<Bill, "id" | "createdAt">>) => void;
  /** Used to dedupe Plaid recurring suggestions against bills already tracked. */
  existingBills?: Bill[];
}

export function BillSuggestions({ onAddBills, existingBills = [] }: BillSuggestionsProps) {
  const [open, setOpen] = useState<null | "gmail" | "plaid">(null);

  return (
    <Card>
      <CardContent className="p-4 flex items-center flex-wrap gap-2">
        <span className="text-sm font-medium">Pull bills from:</span>
        <Button
          variant={open === "gmail" ? "brand" : "outline"}
          size="sm"
          onClick={() => setOpen(open === "gmail" ? null : "gmail")}
        >
          📬 Gmail scan
        </Button>
        <Button
          variant={open === "plaid" ? "brand" : "outline"}
          size="sm"
          onClick={() => setOpen(open === "plaid" ? null : "plaid")}
        >
          🏦 Plaid recurring
        </Button>
        <span className="text-[11px] text-muted-foreground ml-auto">
          Scans are read-only. Each candidate becomes a bill only when you click Add.
        </span>
      </CardContent>
      {open === "gmail" && (
        <GmailScanPanel
          onAddBill={(bill) => onAddBills([bill])}
          onClose={() => setOpen(null)}
        />
      )}
      {open === "plaid" && (
        <PlaidScanPanel
          onAddBill={(bill) => onAddBills([bill])}
          onClose={() => setOpen(null)}
          existingBills={existingBills}
        />
      )}
    </Card>
  );
}

function normalizeVendorName(s: string): string {
  return s
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, " ")
    .replace(/\b(inc|llc|co|corp|company|ltd|the|of|and)\b/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

/** Returns the matching existing bill if the suggestion duplicates one already tracked. */
function findExistingMatch(
  suggestion: { vendor: string; amount: number },
  bills: Bill[]
): Bill | undefined {
  const sName = normalizeVendorName(suggestion.vendor);
  if (!sName) return undefined;
  for (const b of bills) {
    if (b.status === "Cancelled") continue;
    const bName = normalizeVendorName(b.vendor);
    if (!bName) continue;
    const vendorMatch =
      bName === sName ||
      bName.includes(sName) ||
      sName.includes(bName);
    if (!vendorMatch) continue;
    const amtDiff = Math.abs(b.amount - suggestion.amount);
    const amtRel = b.amount > 0 ? amtDiff / b.amount : 1;
    if (amtDiff < 5 || amtRel < 0.1) return b;
  }
  return undefined;
}

// ──────────────────────────────────────────────────────────────────────────
// Gmail scan
// ──────────────────────────────────────────────────────────────────────────

function GmailScanPanel({
  onAddBill,
  onClose,
}: {
  onAddBill: (bill: Omit<Bill, "id" | "createdAt">) => void;
  onClose: () => void;
}) {
  const [candidates, setCandidates] = useState<GmailCandidate[]>([]);
  const [days, setDays] = useState(60);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string>("");
  const [parsing, setParsing] = useState<string | null>(null);
  const [parsed, setParsed] = useState<Record<string, ParsedGmail>>({});
  const [dismissed, setDismissed] = useState<Set<string>>(new Set());

  async function scan() {
    setError("");
    setLoading(true);
    try {
      const resp = await fetch(`/api/bills/scan-gmail?days=${days}`);
      const data = await resp.json();
      if (!resp.ok) throw new Error(data.error || "Scan failed");
      setCandidates(data.candidates ?? []);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Failed");
    } finally {
      setLoading(false);
    }
  }

  async function parseThread(c: GmailCandidate) {
    setParsing(c.threadId);
    try {
      const resp = await fetch("/api/bills/scan-gmail", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ accountId: c.accountId, threadId: c.threadId }),
      });
      const data = await resp.json();
      if (!resp.ok) throw new Error(data.error || "Parse failed");
      setParsed((prev) => ({ ...prev, [c.threadId]: data.extracted as ParsedGmail }));
    } catch (e) {
      setError(e instanceof Error ? e.message : "Parse failed");
    } finally {
      setParsing(null);
    }
  }

  function dismiss(threadId: string) {
    setDismissed((prev) => {
      const next = new Set(prev);
      next.add(threadId);
      return next;
    });
  }

  function addFromCandidate(c: GmailCandidate) {
    const p = parsed[c.threadId];
    const category =
      p?.category && BILL_CATEGORIES.includes(p.category as BillCategory)
        ? (p.category as BillCategory)
        : undefined;
    const bill: Omit<Bill, "id" | "createdAt"> = {
      vendor: p?.vendor || c.fromName || c.fromEmail,
      amount: typeof p?.amount === "number" ? p.amount : 0,
      dueDate: p?.dueDate || new Date().toISOString().slice(0, 10),
      entitySlug: "south-armz-global",
      status: p?.alreadyPaid ? "Paid" : "Unscheduled",
      recurring: false,
      notes:
        `From Gmail: "${c.subject}"` +
        (p?.notes ? ` — ${p.notes}` : "") +
        (p?.confidence ? ` (Claude confidence: ${p.confidence})` : ""),
      accountNumber: p?.accountNumber || undefined,
      category,
      source: "gmail",
      sourceRef: `${c.accountId}:${c.threadId}`,
      attachments: [],
    };
    onAddBill(bill);
    dismiss(c.threadId);
  }

  const visible = candidates.filter((c) => !dismissed.has(c.threadId));

  return (
    <div className="p-4 border-t space-y-3">
      <div className="flex items-center justify-between flex-wrap gap-2">
        <div>
          <h3 className="text-sm font-semibold">Gmail bill scan</h3>
          <p className="text-[11px] text-muted-foreground">
            Searches your connected Gmail accounts for invoice / statement / payment-due emails.
            Each thread is two clicks: extract with Claude, then Add.
          </p>
        </div>
        <div className="flex items-center gap-2">
          <label className="text-xs flex items-center gap-1">
            Days back
            <select
              value={days}
              onChange={(e) => setDays(Number(e.target.value))}
              className="h-8 rounded-md border border-input bg-background px-2 text-xs"
            >
              <option value="14">14</option>
              <option value="30">30</option>
              <option value="60">60</option>
              <option value="90">90</option>
              <option value="180">180</option>
            </select>
          </label>
          <Button variant="brand" size="sm" disabled={loading} onClick={scan}>
            {loading ? "Scanning…" : candidates.length === 0 ? "Run scan" : "Re-scan"}
          </Button>
          <Button variant="ghost" size="sm" onClick={onClose}>
            ×
          </Button>
        </div>
      </div>
      {error && <p className="text-xs text-destructive">{error}</p>}
      {candidates.length === 0 && !loading && !error && (
        <p className="text-xs text-muted-foreground">
          No scan yet. Click <strong>Run scan</strong> above.
        </p>
      )}
      {visible.length > 0 && (
        <div className="space-y-2">
          {visible.map((c) => {
            const p = parsed[c.threadId];
            return (
              <div key={c.threadId} className="rounded-md border bg-background p-3">
                <div className="flex items-start justify-between gap-3 flex-wrap">
                  <div className="min-w-0 flex-1">
                    <div className="text-sm font-medium truncate">{c.subject}</div>
                    <div className="text-[11px] text-muted-foreground truncate">
                      {c.fromName} &lt;{c.fromEmail}&gt; · {c.accountEmail} ·{" "}
                      {new Date(c.internalDateMs).toLocaleDateString()}
                    </div>
                    <div className="mt-1 text-[11px] text-muted-foreground line-clamp-2">
                      {c.snippet}
                    </div>
                    {p && (
                      <div className="mt-2 grid gap-1 md:grid-cols-2 text-[11px]">
                        <div>
                          <strong>Vendor:</strong> {p.vendor ?? "—"}
                        </div>
                        <div>
                          <strong>Amount:</strong>{" "}
                          {typeof p.amount === "number" ? formatCurrency(p.amount) : "—"}
                        </div>
                        <div>
                          <strong>Due:</strong> {p.dueDate ?? "—"}
                        </div>
                        <div>
                          <strong>Category:</strong> {p.category ?? "—"}
                        </div>
                        {p.accountNumber && (
                          <div className="md:col-span-2">
                            <strong>Account #:</strong>{" "}
                            <code className="text-[10px]">{p.accountNumber}</code>
                          </div>
                        )}
                        {(p.alreadyPaid || p.isBill === false) && (
                          <div className="md:col-span-2">
                            <Badge variant={p.isBill === false ? "outline" : "success"}>
                              {p.isBill === false
                                ? "Claude says: not actually a bill"
                                : "Already paid — will create as Paid"}
                            </Badge>
                          </div>
                        )}
                        {p.notes && (
                          <div className="md:col-span-2 text-muted-foreground italic">
                            {p.notes}
                          </div>
                        )}
                      </div>
                    )}
                  </div>
                  <div className="flex flex-col items-end gap-1 shrink-0">
                    {!p ? (
                      <Button
                        variant="outline"
                        size="sm"
                        disabled={parsing === c.threadId}
                        onClick={() => parseThread(c)}
                      >
                        {parsing === c.threadId ? "Reading…" : "✨ Extract"}
                      </Button>
                    ) : (
                      <Button
                        variant="brand"
                        size="sm"
                        disabled={p.isBill === false}
                        onClick={() => addFromCandidate(c)}
                      >
                        + Add bill
                      </Button>
                    )}
                    <Button variant="ghost" size="sm" onClick={() => dismiss(c.threadId)}>
                      Skip
                    </Button>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ──────────────────────────────────────────────────────────────────────────
// Plaid recurring scan
// ──────────────────────────────────────────────────────────────────────────

function PlaidScanPanel({
  onAddBill,
  onClose,
  existingBills = [],
}: {
  onAddBill: (bill: Omit<Bill, "id" | "createdAt">) => void;
  onClose: () => void;
  existingBills?: Bill[];
}) {
  const [accounts] = useLocalStorage<PlaidAccount[]>("sag.plaid.accounts", []);
  const [accessTokens] = useLocalStorage<Record<string, string>>("sag.plaid.accessTokens", {});
  const [suggestions, setSuggestions] = useState<PlaidSuggestion[]>([]);
  const [errors, setErrors] = useState<Array<{ token: string; error: string }>>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string>("");
  const [dismissed, setDismissed] = useState<Set<string>>(new Set());
  const [showTracked, setShowTracked] = useState(false);

  const tokens = Array.from(
    new Set(
      accounts
        .map((a) => accessTokens[a.itemId] ?? a.accessToken)
        .filter((t): t is string => !!t)
    )
  );

  useEffect(() => {
    // Auto-run when the panel opens if at least one bank is linked.
    if (tokens.length === 0) return;
    const t = setTimeout(() => void scan(), 0);
    return () => clearTimeout(t);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  async function scan() {
    setError("");
    setLoading(true);
    try {
      const resp = await fetch("/api/bills/plaid-recurring", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ accessTokens: tokens }),
      });
      const data = await resp.json();
      if (!resp.ok) throw new Error(data.error || "Scan failed");
      setSuggestions(data.suggestions ?? []);
      setErrors(data.errors ?? []);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Failed");
    } finally {
      setLoading(false);
    }
  }

  function addFromSuggestion(s: PlaidSuggestion) {
    const category =
      s.category && BILL_CATEGORIES.includes(s.category as BillCategory)
        ? (s.category as BillCategory)
        : undefined;
    const account = accounts.find((a) => a.accountId === s.plaidAccountId);
    const bill: Omit<Bill, "id" | "createdAt"> = {
      vendor: s.vendor,
      amount: s.amount,
      dueDate: s.nextDueDate || new Date().toISOString().slice(0, 10),
      entitySlug: account?.entitySlug ?? "south-armz-global",
      status: "Scheduled",
      recurring: true,
      recurrencePattern: s.recurrencePattern === "unknown" ? "monthly" : s.recurrencePattern,
      notes: `Detected from Plaid (${s.status}). Last debit ${s.lastDate} from ${
        account?.name ?? "linked account"
      }${account?.mask ? ` ****${account.mask}` : ""}.`,
      category,
      source: "plaid",
      sourceRef: s.plaidStreamId,
      attachments: [],
    };
    onAddBill(bill);
    setDismissed((prev) => {
      const next = new Set(prev);
      next.add(s.plaidStreamId);
      return next;
    });
  }

  // For each suggestion, identify whether it duplicates an existing bill.
  const annotated = suggestions.map((s) => ({
    suggestion: s,
    existing: findExistingMatch({ vendor: s.vendor, amount: s.amount }, existingBills),
  }));

  const newOnly = annotated.filter(
    (a) => !a.existing && !dismissed.has(a.suggestion.plaidStreamId)
  );
  const trackedCount = annotated.filter((a) => !!a.existing).length;
  const dismissedCount = annotated.filter(
    (a) => !a.existing && dismissed.has(a.suggestion.plaidStreamId)
  ).length;

  const visible = showTracked
    ? annotated.filter((a) => !dismissed.has(a.suggestion.plaidStreamId))
    : newOnly;

  return (
    <div className="p-4 border-t space-y-3">
      <div className="flex items-center justify-between flex-wrap gap-2">
        <div>
          <h3 className="text-sm font-semibold">Plaid recurring outflows</h3>
          <p className="text-[11px] text-muted-foreground">
            Uses Plaid&rsquo;s recurring-transactions endpoint to detect subscription-like debits
            across your linked accounts. Suggestions become &ldquo;Scheduled&rdquo; bills on click.
            Streams matching an existing bill (same vendor + amount within $5 / 10%) are
            auto-filtered.
          </p>
        </div>
        <div className="flex items-center gap-2">
          <Button
            variant="brand"
            size="sm"
            disabled={loading || tokens.length === 0}
            onClick={scan}
          >
            {loading ? "Scanning…" : "Re-scan"}
          </Button>
          <Button variant="ghost" size="sm" onClick={onClose}>
            ×
          </Button>
        </div>
      </div>
      {suggestions.length > 0 && (
        <div className="text-[11px] text-muted-foreground flex items-center gap-3 flex-wrap">
          <span>
            <strong>{suggestions.length}</strong> stream{suggestions.length === 1 ? "" : "s"}
            {" "}detected
          </span>
          <span>·</span>
          <span>
            <strong className="text-blue-700 dark:text-blue-400">{newOnly.length}</strong> new
          </span>
          <span>
            <strong className="text-muted-foreground">{trackedCount}</strong> already tracked
          </span>
          {dismissedCount > 0 && (
            <span>
              <strong className="text-muted-foreground">{dismissedCount}</strong> dismissed
            </span>
          )}
          {trackedCount > 0 && (
            <button
              onClick={() => setShowTracked((v) => !v)}
              className="ml-auto text-[10px] underline hover:no-underline"
            >
              {showTracked ? "Hide already-tracked" : `Show ${trackedCount} already-tracked`}
            </button>
          )}
        </div>
      )}
      {tokens.length === 0 ? (
        <Card className="border-dashed">
          <CardContent className="p-4 text-sm text-muted-foreground">
            No Plaid accounts linked yet. Visit <code>/app/finance</code> → Bank accounts to
            link Truist or another bank, then come back.
          </CardContent>
        </Card>
      ) : (
        <>
          {error && <p className="text-xs text-destructive">{error}</p>}
          {errors.length > 0 && (
            <p className="text-[11px] text-yellow-700 dark:text-yellow-400">
              {errors.length} linked bank{errors.length === 1 ? "" : "s"} returned an error —
              try re-linking the affected item if recurring data is missing.
            </p>
          )}
          {visible.length === 0 && !loading && (
            <p className="text-xs text-muted-foreground">
              {suggestions.length === 0
                ? "No recurring outflows detected. Plaid needs 90+ days of transactions to surface a stream — sandbox accounts only have synthetic data."
                : trackedCount > 0 && newOnly.length === 0
                  ? "All recurring streams are already tracked as bills. Re-scan if you've linked a new account."
                  : "All new suggestions imported or dismissed."}
            </p>
          )}
          <div className="space-y-2">
            {visible.map(({ suggestion: s, existing }) => (
              <div
                key={s.plaidStreamId}
                className={`rounded-md border p-3 flex items-start gap-3 flex-wrap ${
                  existing ? "bg-muted/30 opacity-80" : "bg-background"
                }`}
              >
                <div className="min-w-0 flex-1">
                  <div className="flex items-center gap-2 flex-wrap mb-1">
                    <span className="text-sm font-semibold">{s.vendor}</span>
                    <span className="text-sm font-semibold tabular-nums">
                      {formatCurrency(s.amount)}
                    </span>
                    <Badge variant="info">{s.recurrencePattern}</Badge>
                    {s.status === "MATURE" ? (
                      <Badge variant="success" className="text-[10px]">
                        Confirmed
                      </Badge>
                    ) : (
                      <Badge variant="warning" className="text-[10px]">
                        {s.status}
                      </Badge>
                    )}
                    {existing && (
                      <Badge variant="outline" className="text-[10px]">
                        ✓ Already tracked
                      </Badge>
                    )}
                  </div>
                  <div className="text-[11px] text-muted-foreground">
                    Last debit {s.lastDate} · Next due {s.nextDueDate || "—"}
                    {s.category ? ` · ${s.category}` : ""}
                  </div>
                  {existing && (
                    <div className="mt-1 text-[10px] text-muted-foreground italic">
                      Matches existing bill: {existing.vendor} (
                      {formatCurrency(existing.amount)}, due {existing.dueDate},{" "}
                      {existing.status})
                    </div>
                  )}
                </div>
                <div className="flex items-center gap-1 shrink-0">
                  {!existing && (
                    <Button variant="brand" size="sm" onClick={() => addFromSuggestion(s)}>
                      + Add bill
                    </Button>
                  )}
                  <Button
                    variant="ghost"
                    size="sm"
                    onClick={() =>
                      setDismissed((prev) => {
                        const next = new Set(prev);
                        next.add(s.plaidStreamId);
                        return next;
                      })
                    }
                  >
                    Dismiss
                  </Button>
                </div>
              </div>
            ))}
          </div>
        </>
      )}
      <p className="text-[11px] text-muted-foreground">
        Auto-categorized via Plaid&rsquo;s personal-finance taxonomy when available. Edit on the
        bill row after import. Entity defaults to the Plaid account&rsquo;s assigned entity.
      </p>
      <p className="text-[11px] text-muted-foreground">
        Linked sources:{" "}
        {accounts
          .map((a) => `${a.name}${a.mask ? ` ****${a.mask}` : ""}`)
          .join(", ") || "none"}
      </p>
      <p className="text-[11px] text-muted-foreground">
        Mapped entities:{" "}
        {Array.from(
          new Set(accounts.map((a) => a.entitySlug).filter((s): s is string => !!s))
        )
          .map((slug) => ALL_ORGANIZATIONS.find((o) => o.slug === slug)?.name ?? slug)
          .join(", ") || "—"}
      </p>
    </div>
  );
}
