import { useEffect, useMemo, useState } from "react";
import { supabase } from "@/integrations/supabase/client";
import { invokeEdgeFunction } from "@/lib/edgeFunctionUtils";
import { fetchChartOfAccounts, NormalizedAccount } from "@/lib/chartOfAccountsSource";
import { useToast } from "@/hooks/use-toast";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Loader2, Save, ArrowRight, AlertTriangle, Wand2 } from "lucide-react";

interface BuildiumGLAccount {
  id: string;
  name: string;
  account_number: string | null;
  type: string;
  is_active: boolean;
}

interface FlagRow {
  buildium_gl_id: string;
  buildium_name: string | null;
  buildium_number: string | null;
  context: string | null;
}

interface Props {
  associations: { id: string; name: string }[];
}

const normalize = (v: string | null | undefined) =>
  String(v ?? "").toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
// Dashboard names sometimes carry a leading code ("2010 Prepayments")
const normalizeName = (v: string | null | undefined) =>
  normalize(String(v ?? "").replace(/^\s*\d{3,6}(?:[-.]\d+)?\s+/, ""));

/**
 * Precise Buildium GL account ↔ dashboard accounting account mapping.
 * Both the nightly GL pull and charge push/pull resolve accounts ONLY through
 * these links — unmapped accounts hold their transactions until linked here.
 */
export default function BuildiumGLAccountMapCard({ associations }: Props) {
  const { toast } = useToast();
  const [selectedAssociation, setSelectedAssociation] = useState("");
  const [buildiumAccounts, setBuildiumAccounts] = useState<BuildiumGLAccount[]>([]);
  const [dashboardAccounts, setDashboardAccounts] = useState<NormalizedAccount[]>([]);
  // buildium_gl_id -> dashboard account id ("" = unmapped)
  const [selection, setSelection] = useState<Record<string, string>>({});
  // dashboard account id -> buildium_gl_id chosen as push target (when several map to it)
  const [pushTargets, setPushTargets] = useState<Record<string, string>>({});
  const [flags, setFlags] = useState<FlagRow[]>([]);
  const [loading, setLoading] = useState(false);
  const [saving, setSaving] = useState(false);

  useEffect(() => {
    if (!selectedAssociation) {
      setBuildiumAccounts([]); setDashboardAccounts([]); setSelection({}); setPushTargets({}); setFlags([]);
      return;
    }
    loadAll(selectedAssociation);
  }, [selectedAssociation]);

  const loadAll = async (assocId: string) => {
    setLoading(true);
    try {
      const [glRes, dashAccounts, linksRes, flagsRes] = await Promise.all([
        invokeEdgeFunction("buildium-sync", { body: { syncType: "gl_accounts", includeAll: true, selectedAssociationIds: [assocId] } }),
        fetchChartOfAccounts(assocId, "platform"),
        supabase.from("buildium_gl_account_links").select("buildium_gl_id, account_id, is_push_target").eq("association_id", assocId),
        supabase.from("buildium_unmapped_gl_accounts").select("buildium_gl_id, buildium_name, buildium_number, context").eq("association_id", assocId),
      ]);
      if (glRes.error) throw glRes.error;
      if (linksRes.error) throw linksRes.error;

      setBuildiumAccounts((glRes.data?.gl_accounts || []) as BuildiumGLAccount[]);
      setDashboardAccounts(dashAccounts);

      const sel: Record<string, string> = {};
      const targets: Record<string, string> = {};
      for (const row of linksRes.data || []) {
        sel[row.buildium_gl_id] = row.account_id;
        if (row.is_push_target) targets[row.account_id] = row.buildium_gl_id;
      }
      setSelection(sel);
      setPushTargets(targets);
      setFlags((flagsRes.data || []) as FlagRow[]);
    } catch (err: any) {
      toast({ variant: "destructive", title: "Error loading GL account map", description: err.message });
    } finally {
      setLoading(false);
    }
  };

  const flaggedIds = useMemo(() => new Set(flags.map((f) => f.buildium_gl_id)), [flags]);

  const sortedAccounts = useMemo(() => {
    const rank = (a: BuildiumGLAccount) => (flaggedIds.has(a.id) ? 0 : selection[a.id] ? 2 : 1);
    return [...buildiumAccounts].sort((a, b) => {
      const r = rank(a) - rank(b);
      if (r !== 0) return r;
      const an = a.account_number || "";
      const bn = b.account_number || "";
      if (an !== bn) return an.localeCompare(bn, undefined, { numeric: true });
      return a.name.localeCompare(b.name);
    });
  }, [buildiumAccounts, selection, flaggedIds]);

  // dashboard account id -> buildium ids currently mapped to it (for push-target radios)
  const duplicateGroups = useMemo(() => {
    const groups = new Map<string, string[]>();
    for (const [glId, acctId] of Object.entries(selection)) {
      if (!acctId) continue;
      groups.set(acctId, [...(groups.get(acctId) || []), glId]);
    }
    return groups;
  }, [selection]);

  const mappedCount = Object.values(selection).filter(Boolean).length;
  const unmappedCount = buildiumAccounts.length - buildiumAccounts.filter((a) => selection[a.id]).length;

  const setMapping = (glId: string, accountId: string) => {
    setSelection((prev) => {
      const next = { ...prev };
      if (accountId) next[glId] = accountId; else delete next[glId];
      return next;
    });
  };

  const suggestMatches = () => {
    const byCode = new Map<string, NormalizedAccount>();
    const byName = new Map<string, NormalizedAccount>();
    for (const a of dashboardAccounts) {
      if (a.account_number) byCode.set(normalize(a.account_number), a);
      byName.set(normalizeName(a.account_name), a);
    }
    let suggested = 0;
    setSelection((prev) => {
      const next = { ...prev };
      for (const gl of buildiumAccounts) {
        if (next[gl.id]) continue; // never overwrite an existing choice
        const codeMatch = gl.account_number ? byCode.get(normalize(gl.account_number)) : undefined;
        const nameMatch = byName.get(normalizeName(gl.name));
        const match = codeMatch || nameMatch;
        if (match) { next[gl.id] = match.id; suggested++; }
      }
      return next;
    });
    toast({
      title: suggested > 0 ? `${suggested} match(es) suggested` : "No new matches found",
      description: suggested > 0 ? "Review the prefilled rows, then Save to apply." : "Remaining accounts need to be mapped by hand.",
    });
  };

  const saveLinks = async () => {
    if (!selectedAssociation) return;
    setSaving(true);
    try {
      const glById = new Map(buildiumAccounts.map((a) => [a.id, a]));
      // One push target per dashboard account: honor the chosen radio, default first.
      const pushTargetByGl = new Set<string>();
      for (const [acctId, glIds] of duplicateGroups.entries()) {
        const chosen = pushTargets[acctId] && glIds.includes(pushTargets[acctId]) ? pushTargets[acctId] : glIds[0];
        pushTargetByGl.add(chosen);
      }

      const rows = Object.entries(selection)
        .filter(([, acctId]) => acctId)
        .map(([glId, acctId]) => ({
          association_id: selectedAssociation,
          buildium_gl_id: glId,
          buildium_name: glById.get(glId)?.name ?? null,
          buildium_number: glById.get(glId)?.account_number ?? null,
          buildium_type: glById.get(glId)?.type ?? null,
          account_id: acctId,
          is_push_target: pushTargetByGl.has(glId),
          updated_at: new Date().toISOString(),
        }));

      // Replace the association's links wholesale — simplest way to honor the
      // unique push-target index across re-mappings.
      const { error: delErr } = await supabase.from("buildium_gl_account_links").delete().eq("association_id", selectedAssociation);
      if (delErr) throw delErr;
      if (rows.length > 0) {
        const { error: insErr } = await supabase.from("buildium_gl_account_links").insert(rows);
        if (insErr) throw insErr;
      }

      // Clear "needs mapping" flags that are now resolved.
      const mappedIds = rows.map((r) => r.buildium_gl_id);
      if (mappedIds.length > 0) {
        await supabase.from("buildium_unmapped_gl_accounts").delete()
          .eq("association_id", selectedAssociation)
          .in("buildium_gl_id", mappedIds);
      }
      setFlags((prev) => prev.filter((f) => !mappedIds.includes(f.buildium_gl_id)));

      toast({ title: "GL account map saved", description: `${rows.length} link(s) saved.` });
    } catch (err: any) {
      toast({ variant: "destructive", title: "Save failed", description: err.message });
    } finally {
      setSaving(false);
    }
  };

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-lg">Buildium ↔ Dashboard GL Account Map</CardTitle>
        <CardDescription>
          Link each Buildium GL account to the exact dashboard accounting account it represents.
          Pull and push syncs resolve accounts <strong>only</strong> through these links — transactions touching an
          unmapped Buildium account are held until you map it here.
        </CardDescription>
      </CardHeader>
      <CardContent className="space-y-4">
        <div className="flex flex-wrap items-center gap-3">
          <Select value={selectedAssociation} onValueChange={setSelectedAssociation}>
            <SelectTrigger className="w-[300px]">
              <SelectValue placeholder="Select association..." />
            </SelectTrigger>
            <SelectContent>
              {associations.map((a) => (
                <SelectItem key={a.id} value={a.id}>{a.name}</SelectItem>
              ))}
            </SelectContent>
          </Select>
          {selectedAssociation && !loading && (
            <>
              <Badge variant="outline" className="text-xs">{mappedCount} mapped</Badge>
              {unmappedCount > 0 && (
                <Badge variant="outline" className="text-xs text-amber-600 border-amber-200 bg-amber-50">{unmappedCount} unmapped</Badge>
              )}
            </>
          )}
        </div>

        {selectedAssociation && (
          loading ? (
            <div className="flex items-center gap-2 text-sm text-muted-foreground py-4">
              <Loader2 className="h-4 w-4 animate-spin" /> Loading accounts and links...
            </div>
          ) : (
            <div className="space-y-4">
              {dashboardAccounts.length === 0 && (
                <p className="text-sm text-muted-foreground bg-muted p-3 rounded-lg">
                  No platform accounting accounts found for this association — it may not be on the accounting platform yet.
                </p>
              )}

              {flags.length > 0 && (
                <div className="border border-amber-200 bg-amber-50 rounded-lg p-3 text-sm">
                  <p className="font-medium text-amber-800 flex items-center gap-2">
                    <AlertTriangle className="h-4 w-4" /> {flags.length} account(s) blocked a recent sync
                  </p>
                  <ul className="mt-1 text-xs text-amber-700 list-disc pl-5">
                    {flags.slice(0, 8).map((f) => (
                      <li key={f.buildium_gl_id}>
                        {[f.buildium_number, f.buildium_name || `Buildium GL ${f.buildium_gl_id}`].filter(Boolean).join(" - ")}
                        {f.context ? ` (${f.context.replace(/_/g, " ")})` : ""}
                      </li>
                    ))}
                    {flags.length > 8 && <li>…and {flags.length - 8} more</li>}
                  </ul>
                  <p className="text-xs text-amber-700 mt-1">They're sorted to the top below. Map and save, then re-run the sync.</p>
                </div>
              )}

              <div className="flex justify-end">
                <Button variant="outline" size="sm" className="gap-2" onClick={suggestMatches} disabled={dashboardAccounts.length === 0}>
                  <Wand2 className="h-3.5 w-3.5" /> Suggest matches
                </Button>
              </div>

              <div className="border rounded-lg divide-y max-h-[480px] overflow-y-auto">
                {sortedAccounts.map((gl) => {
                  const acctId = selection[gl.id] || "";
                  const group = acctId ? duplicateGroups.get(acctId) || [] : [];
                  const showPushRadio = group.length > 1;
                  const isPushTarget = showPushRadio
                    ? (pushTargets[acctId] && group.includes(pushTargets[acctId]) ? pushTargets[acctId] : group[0]) === gl.id
                    : false;
                  return (
                    <div key={gl.id} className="flex items-center gap-2 px-3 py-2">
                      <div className="w-[40%] min-w-0">
                        <p className="text-sm truncate">
                          {[gl.account_number, gl.name].filter(Boolean).join(" - ")}
                        </p>
                        <p className="text-[11px] text-muted-foreground">
                          {gl.type}{!gl.is_active && " · inactive"}
                          {flaggedIds.has(gl.id) && <span className="text-amber-600"> · blocked a sync</span>}
                        </p>
                      </div>
                      <ArrowRight className="h-4 w-4 text-muted-foreground shrink-0" />
                      <Select value={acctId || "__none__"} onValueChange={(v) => setMapping(gl.id, v === "__none__" ? "" : v)}>
                        <SelectTrigger className="flex-1 h-8 text-xs">
                          <SelectValue placeholder="Select dashboard account..." />
                        </SelectTrigger>
                        <SelectContent>
                          <SelectItem value="__none__">— Not mapped —</SelectItem>
                          {dashboardAccounts.map((a) => (
                            <SelectItem key={a.id} value={a.id}>
                              {[a.account_number, a.account_name].filter(Boolean).join(" - ")} ({a.account_type})
                            </SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                      {showPushRadio && (
                        <label className="flex items-center gap-1 text-[11px] text-muted-foreground shrink-0 cursor-pointer" title="Several Buildium accounts map to this dashboard account — pick which one charges are pushed to.">
                          <input
                            type="radio"
                            name={`push-${acctId}`}
                            checked={isPushTarget}
                            onChange={() => setPushTargets((prev) => ({ ...prev, [acctId]: gl.id }))}
                          />
                          push
                        </label>
                      )}
                    </div>
                  );
                })}
                {sortedAccounts.length === 0 && (
                  <p className="text-sm text-muted-foreground p-4">No Buildium GL accounts returned. Check the Buildium connection.</p>
                )}
              </div>

              <div className="flex justify-end pt-1">
                <Button onClick={saveLinks} disabled={saving} className="gap-2">
                  {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
                  Save GL Account Map
                </Button>
              </div>
            </div>
          )
        )}
      </CardContent>
    </Card>
  );
}
