'use client';

import { useState, useEffect, useRef, useCallback } from 'react';
import { toast } from 'sonner';
import {
  AlertTriangle, Building2, CheckCircle2, CalendarDays, Loader2, Plus, Tag, X,
} from 'lucide-react';
import { api } from '@/api';
import { cn, fmt, errMessage } from '@/lib/utils';
import { asDollars } from '@/lib/money';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import BillHistoricalImportDialog from '@/components/BillHistoricalImportDialog';

interface MerchantRule {
  id: number | string;
  merchant?: string;
  auto_attribute_late?: number;
}

interface Conflict {
  id: number | string;
  name?: string;
}

interface Suggestion {
  id: number | string;
  label?: string;
  normalized?: string;
  amount?: number;
  date?: string;
}

interface SubTx {
  id: number | string;
  payee?: string | null;
  description?: string | null;
  memo?: string | null;
  merchant?: string | null;
  amount?: number;
  posted_date?: string | null;
  match_status?: string | null;
}

// Debounce helper
function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const t = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(t);
  }, [value, delay]);
  return debounced;
}

function RuleChip({ rule, onDelete, onToggleAutoAttr, deleting, togglingAutoAttr }: {
  rule: MerchantRule;
  onDelete: (rule: MerchantRule) => void;
  onToggleAutoAttr: (rule: MerchantRule, enabled: boolean) => void;
  deleting: number | string | null;
  togglingAutoAttr: number | string | null;
}) {
  return (
    <div className="flex items-center justify-between gap-2 rounded-lg border border-border/60 bg-muted/20 px-3 py-2">
      <div className="flex items-center gap-1.5 min-w-0">
        <Tag className="h-3 w-3 shrink-0 text-primary" />
        <span className="text-xs font-medium text-foreground truncate max-w-[10rem]" title={rule.merchant}>
          {rule.merchant}
        </span>
      </div>
      <div className="flex items-center gap-2 shrink-0">
        {/* Auto-attribute late payment toggle */}
        <button
          type="button"
          onClick={() => onToggleAutoAttr(rule, !rule.auto_attribute_late)}
          disabled={togglingAutoAttr === rule.id}
          title={rule.auto_attribute_late
            ? 'Auto-fixing month crossing is ON — payments in the first 5 days are automatically moved to the prior month'
            : 'Turn on to automatically move payments that post in the first 5 days of a month to the prior month (e.g. AT&T posts June 1, belongs to May)'}
          className={cn(
            'flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-semibold transition-colors',
            rule.auto_attribute_late
              ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 hover:bg-emerald-500/20'
              : 'border-border/50 bg-background text-muted-foreground hover:bg-muted/40',
          )}
        >
          {togglingAutoAttr === rule.id
            ? <Loader2 className="h-2.5 w-2.5 animate-spin" />
            : <CalendarDays className="h-2.5 w-2.5" />}
          {rule.auto_attribute_late ? 'Auto-fix on' : 'Auto-fix'}
        </button>
        <button
          type="button"
          onClick={() => onDelete(rule)}
          disabled={deleting === rule.id}
          className="rounded-full p-0.5 text-muted-foreground/60 transition-opacity hover:text-destructive disabled:opacity-30"
          aria-label={`Remove rule "${rule.merchant}"`}
        >
          {deleting === rule.id ? <Loader2 className="h-3 w-3 animate-spin" /> : <X className="h-3 w-3" />}
        </button>
      </div>
    </div>
  );
}

function ConflictWarning({ conflicts }: { conflicts?: Conflict[] }) {
  if (!conflicts?.length) return null;
  return (
    <div className="flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/8 px-3 py-2 text-xs text-amber-700 dark:text-amber-400">
      <AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0" />
      <span>
        This pattern is already used by{' '}
        {conflicts.map((c, i) => (
          <span key={c.id}>
            {i > 0 && ', '}
            <strong>{c.name}</strong>
          </span>
        ))}.
        Transactions could match both bills — consider making your pattern more specific.
      </span>
    </div>
  );
}

function PreviewBadge({ count, loading, error }: { count: number | null; loading: boolean; error: boolean }) {
  if (loading) return <Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground" />;
  if (error)   return <span className="rounded-full border border-destructive/30 bg-destructive/10 px-2 py-0.5 text-[10px] font-semibold text-destructive">Error</span>;
  if (count === null) return null;
  return (
    <span className={cn(
      'rounded-full border px-2 py-0.5 text-[10px] font-semibold tabular-nums',
      count > 0
        ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
        : 'border-border/60 bg-muted/40 text-muted-foreground',
    )}>
      {count === 0 ? 'No matches' : `${count} match${count === 1 ? '' : 'es'}`}
    </span>
  );
}

export default function BillMerchantRules({ billId, billName, onRulesChanged }: {
  billId: number | string;
  billName?: string;
  onRulesChanged?: () => void;
}) {
  const [rules, setRules]             = useState<MerchantRule[]>([]);
  const [suggestions, setSuggestions] = useState<Suggestion[]>([]);
  const [loading, setLoading]         = useState(true);
  const [deleting, setDeleting]       = useState<number | string | null>(null);
  const [adding, setAdding]           = useState(false);
  const [input, setInput]             = useState('');
  const [showSuggestions, setShowSuggestions] = useState(false);
  const [previewCount, setPreviewCount]       = useState<number | null>(null);
  const [previewLoading, setPreviewLoading]   = useState(false);
  const [previewError, setPreviewError]       = useState(false);
  const [conflicts, setConflicts]             = useState<Conflict[]>([]);
  const [retroFeedback, setRetroFeedback]               = useState<number | null>(null);
  const [showHistoricalDialog, setShowHistoricalDialog] = useState(false);
  const [togglingAutoAttr, setTogglingAutoAttr]         = useState<number | string | null>(null);
  const [liveResults, setLiveResults]         = useState<Suggestion[]>([]);
  const [liveSearching, setLiveSearching]     = useState(false);
  const inputRef = useRef<HTMLInputElement>(null);

  const debouncedInput = useDebounce(input.trim(), 380);

  const load = useCallback(async () => {
    if (!billId) return;
    setLoading(true);
    try {
      const data = await api.billMerchantRules(billId) as { rules?: MerchantRule[]; suggestions?: Suggestion[] };
      setRules(data.rules || []);
      setSuggestions(data.suggestions || []);
    } catch {
      // non-fatal
    } finally {
      setLoading(false);
    }
  }, [billId]);

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

  // Preview debounced input
  useEffect(() => {
    if (!debouncedInput || debouncedInput.length < 2) {
      setPreviewCount(null);
      setConflicts([]);
      return;
    }
    let cancelled = false;
    setPreviewLoading(true);
    setPreviewError(false);
    api.previewMerchantRule(billId, debouncedInput)
      .then(raw => {
        if (cancelled) return;
        const data = raw as { match_count?: number; conflicts?: Conflict[] };
        setPreviewCount(data.match_count ?? null);
        setConflicts(data.conflicts || []);
      })
      .catch(() => {
        if (!cancelled) setPreviewError(true);
      })
      .finally(() => { if (!cancelled) setPreviewLoading(false); });
    return () => { cancelled = true; };
  }, [debouncedInput, billId]);

  // Live transaction search when user types something
  useEffect(() => {
    if (!debouncedInput || debouncedInput.length < 2) {
      setLiveResults([]);
      return;
    }
    let cancelled = false;
    setLiveSearching(true);
    api.subscriptionTransactionMatches({ q: debouncedInput, limit: 20 })
      .then(raw => {
        if (cancelled) return;
        const data = raw as SubTx[] | { transactions?: SubTx[] };
        const rows: SubTx[] = Array.isArray(data) ? data : (data?.transactions ?? []);
        setLiveResults(rows.filter(tx => tx.match_status !== 'matched').map(tx => ({
          id: tx.id,
          label: tx.payee || tx.description || tx.memo || '',
          normalized: tx.merchant || (tx.payee || tx.description || tx.memo || '').toLowerCase(),
          amount: tx.amount,
          date: tx.posted_date || '',
        })).filter(s => s.label));
      })
      .catch(() => { if (!cancelled) setLiveResults([]); })
      .finally(() => { if (!cancelled) setLiveSearching(false); });
    return () => { cancelled = true; };
  }, [debouncedInput]);

  // Popover handles its own outside-click dismissal — no manual handler needed

  async function handleAdd(merchantText?: string) {
    const text = (merchantText || input).trim();
    if (!text) return;
    setAdding(true);
    setRetroFeedback(null);
    try {
      const result = await api.addMerchantRule(billId, text) as { rule?: MerchantRule };
      setRules(prev => {
        if (prev.some(r => r.id === result.rule?.id)) return prev;
        return [...prev, result.rule].filter(Boolean) as MerchantRule[];
      });
      setInput('');
      setPreviewCount(null);
      setConflicts([]);
      setShowSuggestions(false);
      toast.success('Rule added');
      // Open the historical import dialog — lets user decide how to handle past transactions
      setShowHistoricalDialog(true);
      onRulesChanged?.();
    } catch (err) {
      toast.error(errMessage(err, 'Failed to add rule'));
    } finally {
      setAdding(false);
    }
  }

  async function handleDelete(rule: MerchantRule) {
    setDeleting(rule.id);
    try {
      await api.deleteMerchantRule(billId, rule.id);
      setRules(prev => prev.filter(r => r.id !== rule.id));
      toast.success(`Rule "${rule.merchant}" removed`);
      onRulesChanged?.();
    } catch (err) {
      toast.error(errMessage(err, 'Failed to remove rule'));
    } finally {
      setDeleting(null);
    }
  }

  async function handleToggleAutoAttr(rule: MerchantRule, enabled: boolean) {
    setTogglingAutoAttr(rule.id);
    try {
      await api.toggleRuleAutoAttribute(billId, rule.id, enabled);
      setRules(prev => prev.map(r => r.id === rule.id ? { ...r, auto_attribute_late: enabled ? 1 : 0 } : r));
      toast.success(enabled
        ? `Auto-fix on — ${rule.merchant} payments will automatically count for the prior month`
        : 'Auto-fix off');
    } catch (err) {
      toast.error(errMessage(err, 'Failed to update rule'));
    } finally {
      setTogglingAutoAttr(null);
    }
  }

  function pickSuggestion(s: Suggestion) {
    setInput(s.label || '');
    setShowSuggestions(false);
    inputRef.current?.focus();
  }

  // When typing: use live search results. When blank: use pre-loaded recent 30.
  const filteredSuggestions = (input.trim().length >= 2 ? liveResults : suggestions.filter(s =>
    !rules.some(r => r.merchant === s.normalized)
  )).slice(0, 10);

  if (loading) {
    return (
      <div className="flex items-center gap-2 px-1 py-3 text-xs text-muted-foreground">
        <Loader2 className="h-3.5 w-3.5 animate-spin" />
        Loading matching rules…
      </div>
    );
  }

  return (
    <div className="space-y-3">

      {/* Existing rules */}
      {rules.length > 0 && (
        <div className="space-y-1.5">
          {rules.map(rule => (
            <RuleChip
              key={rule.id}
              rule={rule}
              onDelete={handleDelete}
              onToggleAutoAttr={handleToggleAutoAttr}
              deleting={deleting}
              togglingAutoAttr={togglingAutoAttr}
            />
          ))}
        </div>
      )}

      {/* Retroactive feedback */}
      {retroFeedback !== null && (
        <div className="flex items-center gap-2 rounded-md border border-emerald-500/25 bg-emerald-500/8 px-3 py-2 text-xs text-emerald-700 dark:text-emerald-400">
          <CheckCircle2 className="h-3.5 w-3.5 shrink-0" />
          {retroFeedback} existing payment{retroFeedback === 1 ? '' : 's'} imported from your transaction history.
        </div>
      )}

      {/* Add rule input */}
      <div className="relative">
        <div className="flex gap-2">
          <div className="relative flex-1">
            <Input
              ref={inputRef}
              value={input}
              onChange={e => { setInput(e.target.value); setShowSuggestions(true); setRetroFeedback(null); setPreviewError(false); }}
              onFocus={() => setShowSuggestions(true)}
              onKeyDown={e => {
                if (e.key === 'Enter') { e.preventDefault(); handleAdd(); }
                if (e.key === 'Escape') setShowSuggestions(false);
              }}
              placeholder="Type merchant name or pick from recent transactions…"
              className="h-8 pr-20 text-xs"
              disabled={adding}
            />
            <div className="absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-1">
              <PreviewBadge count={previewCount} loading={previewLoading} error={previewError} />
            </div>
          </div>
          <Button
            type="button"
            size="sm"
            className="h-8 gap-1.5 px-3 text-xs"
            disabled={adding || input.trim().length < 2}
            onClick={() => handleAdd()}
          >
            {adding ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Plus className="h-3.5 w-3.5" />}
            Add
          </Button>
        </div>

        {/* Suggestions — inline block, no absolute positioning.
            Avoids overflow-y-auto clipping AND Radix Dialog pointer-event capture. */}
        {showSuggestions && (liveSearching || filteredSuggestions.length > 0) && (
          <div className="overflow-hidden rounded-lg border border-border/80 bg-card shadow-sm">
            <p className="flex items-center gap-2 border-b border-border/50 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
              {input.trim().length >= 2 ? 'Matching transactions' : 'Recent unmatched transactions'}
              {liveSearching && <Loader2 className="h-3 w-3 animate-spin" />}
            </p>
            <div className="max-h-40 overflow-y-auto">
              {filteredSuggestions.map(s => {
                const amountVal = Math.abs(Number(s.amount || 0)) / 100;
                return (
                  <button
                    key={s.id}
                    type="button"
                    className="flex w-full items-center gap-3 px-3 py-2 text-left text-xs hover:bg-muted/50 focus:bg-muted/50 focus:outline-none"
                    onMouseDown={e => { e.preventDefault(); pickSuggestion(s); }}
                  >
                    <Building2 className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
                    <span className="min-w-0 flex-1 truncate font-medium">{s.label}</span>
                    <span className="shrink-0 font-mono text-muted-foreground tabular-nums">
                      {fmt(asDollars(amountVal))}
                    </span>
                  </button>
                );
              })}
              {!liveSearching && input.trim().length >= 2 && filteredSuggestions.length === 0 && (
                <p className="px-3 py-3 text-xs text-muted-foreground">No transactions found for "{input.trim()}"</p>
              )}
            </div>
          </div>
        )}

        {/* Conflict warning */}
        {conflicts.length > 0 && input.trim().length >= 2 && (
          <div className="mt-1.5">
            <ConflictWarning conflicts={conflicts} />
          </div>
        )}
      </div>

      {/* Empty state */}
      {rules.length === 0 && !input && (
        <p className="text-[11px] text-muted-foreground/70">
          No rules yet. Type a merchant name or pick a recent transaction above to automatically import future payments for this bill.
        </p>
      )}

      {/* Historical import dialog — fires after a rule is added */}
      <BillHistoricalImportDialog
        billId={billId}
        billName={billName || 'this bill'}
        open={showHistoricalDialog}
        onClose={() => setShowHistoricalDialog(false)}
        onImported={() => {
          setShowHistoricalDialog(false);
          onRulesChanged?.();
        }}
      />
    </div>
  );
}
