"use client";

import { useMemo } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  AlertCard,
  AlertCardSummary,
} from "@/components/ui/alert-card";
import { useLocalStorage } from "@/lib/hooks/use-local-storage";
import { ALL_ORGANIZATIONS } from "@/lib/sag/entities";
import { cn, formatCurrency } from "@/lib/utils";
import type { Bill } from "@/lib/finance/bill-types";
import {
  detectBillCashConflicts,
  type BillCashConflict,
  type BillCashConflictSeverity,
} from "@/lib/finance/detect-bill-cash-conflicts";

/**
 * F4 — Bill-vs-cash conflict detector (UI).
 *
 * Reads `sag.bills.v2` and `sag.plaid.accounts` and runs them through the
 * pure `detectBillCashConflicts` helper. Renders a collapsible alert card at
 * the top of the bills inbox, bucketed by severity (critical / warning /
 * advisory) with a per-conflict "Snooze 7 days" action.
 *
 * LocalStorage keys
 *   Read:  `sag.bills.v2`              (BillsInbox owner)
 *          `sag.plaid.accounts`        (PlaidConnect owner)
 *   Write: `sag.finance.bill_conflict_snoozes`  (owned here)
 */

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

interface SnoozeEntry {
  billId: string;
  /** ISO date (YYYY-MM-DD) — conflict re-surfaces on or after this date. */
  until: string;
}

const SEVERITY_LABEL: Record<BillCashConflictSeverity, string> = {
  critical: "Critical",
  warning: "Tight",
  advisory: "Heads-up",
};

const SEVERITY_BADGE: Record<
  BillCashConflictSeverity,
  "destructive" | "warning" | "outline"
> = {
  critical: "destructive",
  warning: "warning",
  advisory: "outline",
};

const SEVERITY_DESCRIPTION: Record<BillCashConflictSeverity, string> = {
  critical: "Insufficient cash projected at the due date.",
  warning: "Cash covers the bill but the cushion falls below 1.5x.",
  advisory: "This bill is fine, but an upcoming streak will get tight.",
};

const SEVERITY_BUCKET_CLASS: Record<BillCashConflictSeverity, string> = {
  critical: "border-destructive/40 bg-destructive/5",
  warning: "border-yellow-500/40 bg-yellow-500/5",
  advisory: "border-muted-foreground/30 bg-muted/30",
};

function todayIso(): string {
  return new Date().toISOString().slice(0, 10);
}

function addDaysIso(iso: string, days: number): string {
  const d = new Date(`${iso}T00:00:00Z`);
  d.setUTCDate(d.getUTCDate() + days);
  return d.toISOString().slice(0, 10);
}

function entityLabel(slug: string): string {
  const org = ALL_ORGANIZATIONS.find((o) => o.slug === slug);
  if (!org) return slug;
  return `${org.emoji ?? "🏢"} ${org.name}`;
}

function dueLabel(days: number): string {
  if (days === 0) return "today";
  if (days === 1) return "tomorrow";
  if (days > 0) return `in ${days} days`;
  if (days === -1) return "1 day overdue";
  return `${Math.abs(days)} days overdue`;
}

export function BillCashConflicts() {
  const [bills] = useLocalStorage<Bill[]>("sag.bills.v2", []);
  const [accounts] = useLocalStorage<PlaidAccount[]>("sag.plaid.accounts", []);
  const [snoozes, setSnoozes] = useLocalStorage<SnoozeEntry[]>(
    "sag.finance.bill_conflict_snoozes",
    []
  );

  const today = useMemo(() => todayIso(), []);

  // Drop expired snoozes lazily — they're cheap to keep but clutter the inbox.
  const activeSnoozes = useMemo(() => {
    const map = new Map<string, string>();
    for (const s of snoozes) {
      if (s.until > today) map.set(s.billId, s.until);
    }
    return map;
  }, [snoozes, today]);

  const allConflicts = useMemo(
    () => detectBillCashConflicts(bills, accounts, { today }),
    [bills, accounts, today]
  );

  const visibleConflicts = useMemo(
    () => allConflicts.filter((c) => !activeSnoozes.has(c.billId)),
    [allConflicts, activeSnoozes]
  );

  const snoozedCount = allConflicts.length - visibleConflicts.length;

  const buckets = useMemo(() => {
    const groups: Record<BillCashConflictSeverity, BillCashConflict[]> = {
      critical: [],
      warning: [],
      advisory: [],
    };
    for (const c of visibleConflicts) groups[c.severity].push(c);
    return groups;
  }, [visibleConflicts]);

  const totals = useMemo(() => {
    let amountAtRisk = 0;
    for (const c of visibleConflicts) {
      if (c.severity === "critical" || c.severity === "warning") {
        amountAtRisk += c.amount;
      }
    }
    return { amountAtRisk };
  }, [visibleConflicts]);

  const hasCritical = buckets.critical.length > 0;
  const hasAny = visibleConflicts.length > 0;

  function snoozeBill(billId: string) {
    const until = addDaysIso(today, 7);
    setSnoozes((prev) => {
      const filtered = prev.filter((s) => s.billId !== billId);
      return [...filtered, { billId, until }];
    });
  }

  // ────────────────────────────────────────────────────────────────────
  // Empty state — collapsed-by-default green banner.
  // ────────────────────────────────────────────────────────────────────

  if (!hasAny) {
    return (
      <AlertCard
        severity="success"
        title="All projected bills covered"
        hideChip
        icon={<span aria-hidden>✓</span>}
        trailing={
          snoozedCount > 0 ? (
            <Badge variant="outline" className="text-[10px]">
              {snoozedCount} snoozed
            </Badge>
          ) : undefined
        }
      >
        <div className="text-[11px] text-muted-foreground space-y-1">
          <p>
            Every unpaid bill has a projected cash cushion of at least 1.5x its amount on
            the due date, accounting for earlier-due bills at the same entity drawing
            down the balance first.
          </p>
          <p>
            Sources: <code>sag.bills.v2</code> (forward bills only — paid &amp; cancelled
            excluded) and <code>sag.plaid.accounts</code> (entity-tagged balances).
          </p>
        </div>
      </AlertCard>
    );
  }

  // ────────────────────────────────────────────────────────────────────
  // Active conflicts.
  // ────────────────────────────────────────────────────────────────────

  const dominantSeverity: "critical" | "warning" | "advisory" = hasCritical
    ? "critical"
    : buckets.warning.length > 0
      ? "warning"
      : "advisory";

  const titleText = `${visibleConflicts.length} bill-vs-cash conflict${
    visibleConflicts.length === 1 ? "" : "s"
  }`;

  return (
    <AlertCard
      severity={dominantSeverity}
      title={titleText}
      hideChip
      defaultOpen={hasCritical}
      icon={
        <span className="text-lg" aria-hidden>
          {hasCritical ? "🚨" : "⚠️"}
        </span>
      }
      trailing={
        <span className="text-[11px] text-muted-foreground hidden sm:inline">
          {formatCurrency(totals.amountAtRisk)} at risk
        </span>
      }
    >
      <div className="space-y-4 pt-3">
        <AlertCardSummary>
          {buckets.critical.length > 0 && (
            <Badge variant="destructive" className="text-[10px]">
              {buckets.critical.length} critical
            </Badge>
          )}
          {buckets.warning.length > 0 && (
            <Badge variant="warning" className="text-[10px]">
              {buckets.warning.length} tight
            </Badge>
          )}
          {buckets.advisory.length > 0 && (
            <Badge variant="outline" className="text-[10px]">
              {buckets.advisory.length} heads-up
            </Badge>
          )}
          {snoozedCount > 0 && (
            <Badge variant="outline" className="text-[10px]">
              {snoozedCount} snoozed
            </Badge>
          )}
        </AlertCardSummary>

        {(["critical", "warning", "advisory"] as const).map((sev) => {
          const items = buckets[sev];
          if (items.length === 0) return null;
          return (
            <div
              key={sev}
              className={cn("rounded-md border p-3", SEVERITY_BUCKET_CLASS[sev])}
            >
              <div className="flex items-center justify-between gap-2 mb-2">
                <div className="flex items-center gap-2 min-w-0">
                  <Badge variant={SEVERITY_BADGE[sev]} className="text-[10px]">
                    {SEVERITY_LABEL[sev]}
                  </Badge>
                  <span className="text-[11px] text-muted-foreground truncate">
                    {SEVERITY_DESCRIPTION[sev]}
                  </span>
                </div>
                <span className="text-[10px] text-muted-foreground shrink-0">
                  {items.length} bill{items.length === 1 ? "" : "s"}
                </span>
              </div>

              <div className="overflow-x-auto">
                <table className="w-full min-w-[640px] text-xs">
                  <thead className="section-label">
                    <tr className="border-b border-border/60">
                      <th className="py-1.5 pr-2 text-left font-medium">Vendor</th>
                      <th className="py-1.5 px-2 text-left font-medium">Entity</th>
                      <th className="py-1.5 px-2 text-left font-medium">Due</th>
                      <th className="py-1.5 px-2 text-right font-medium">Amount</th>
                      <th className="py-1.5 px-2 text-right font-medium">Projected</th>
                      <th className="py-1.5 px-2 text-right font-medium">Coverage</th>
                      <th className="py-1.5 pl-2 text-right font-medium"></th>
                    </tr>
                  </thead>
                  <tbody>
                    {items.map((c) => (
                      <tr
                        key={c.billId}
                        className="border-b border-border/40 last:border-0 align-top"
                      >
                        <td className="py-2 pr-2">
                          <div className="font-medium">{c.vendor}</div>
                          {c.note && (
                            <div className="text-[10px] text-muted-foreground mt-0.5 max-w-xs">
                              {c.note}
                            </div>
                          )}
                        </td>
                        <td className="py-2 px-2 text-muted-foreground">
                          <span className="truncate inline-block max-w-[10rem]">
                            {entityLabel(c.entitySlug)}
                          </span>
                        </td>
                        <td className="py-2 px-2">
                          <div className="font-mono text-[11px]">{c.dueDate}</div>
                          <div
                            className={cn(
                              "text-[10px]",
                              c.daysUntilDue < 0
                                ? "text-destructive"
                                : "text-muted-foreground"
                            )}
                          >
                            {dueLabel(c.daysUntilDue)}
                          </div>
                        </td>
                        <td className="py-2 px-2 text-right tabular-nums font-medium">
                          {formatCurrency(c.amount)}
                        </td>
                        <td
                          className={cn(
                            "py-2 px-2 text-right tabular-nums",
                            c.projectedBalanceAtDue < 0 && "text-destructive font-medium"
                          )}
                        >
                          {formatCurrency(c.projectedBalanceAtDue)}
                        </td>
                        <td
                          className={cn(
                            "py-2 px-2 text-right tabular-nums font-mono",
                            c.severity === "critical"
                              ? "text-destructive font-semibold"
                              : c.severity === "warning"
                                ? "text-yellow-700 dark:text-yellow-400"
                                : "text-muted-foreground"
                          )}
                        >
                          {Number.isFinite(c.coverageRatio)
                            ? `${c.coverageRatio.toFixed(2)}x`
                            : "—"}
                        </td>
                        <td className="py-2 pl-2 text-right">
                          <Button
                            size="sm"
                            variant="outline"
                            className="h-7 text-[10px] px-2"
                            onClick={() => snoozeBill(c.billId)}
                            title="Hide this conflict for 7 days"
                          >
                            Snooze 7d
                          </Button>
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </div>
          );
        })}

        <p className="text-[11px] text-muted-foreground leading-relaxed">
          <strong className="text-foreground">Why am I seeing this?</strong> Each unpaid
          bill is checked against the cash that&apos;ll actually be available at the
          entity on its due date — walking forward from current Plaid balances and
          deducting earlier-due bills first. Anything with less than a 1.5x cushion
          (projected balance ÷ bill amount) gets flagged: under 1x is critical, 1x to
          1.5x is tight, and a comfortable bill becomes advisory when an upcoming
          streak at the same entity will tip it below the 1.5x line. Paid &amp;
          cancelled bills are excluded. Snoozing hides a conflict for 7 days; it
          re-surfaces automatically.
        </p>
      </div>
    </AlertCard>
  );
}
