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, Plus, ArrowRight, AlertTriangle, Wand2, Trash2 } from "lucide-react";

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

interface LinkRow {
  id: string;
  buildium_gl_id: string;
  buildium_name: string | null;
  buildium_number: string | null;
  buildium_type: string | null;
  account_id: string;
  is_push_target: boolean;
}

interface FlagRow {
  buildium_gl_id: string;
  buildium_name: string | null;
  buildium_number: string | null;
  buildium_type: 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+/, ""));

const glLabel = (a: { account_number?: string | null; buildium_number?: string | null; name?: string | null; buildium_name?: string | null }) =>
  [a.account_number ?? a.buildium_number, a.name ?? a.buildium_name].filter(Boolean).join(" - ") || "Unknown";

/**
 * 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.
 * Mappings are added one at a time (Buildium dropdown → system dropdown → Add);
 * only ACTIVE Buildium accounts are listed.
 */
export default function BuildiumGLAccountMapCard({ associations }: Props) {
  const { toast } = useToast();
  const [selectedAssociation, setSelectedAssociation] = useState("");
  const [buildiumAccounts, setBuildiumAccounts] = useState<BuildiumGLAccount[]>([]);
  const [dashboardAccounts, setDashboardAccounts] = useState<NormalizedAccount[]>([]);
  const [links, setLinks] = useState<LinkRow[]>([]);
  const [flags, setFlags] = useState<FlagRow[]>([]);
  const [loading, setLoading] = useState(false);
  const [busy, setBusy] = useState(false);

  // The "add a mapping" row
  const [addGlId, setAddGlId] = useState("");
  const [addAccountId, setAddAccountId] = useState("");

  useEffect(() => {
    if (!selectedAssociation) {
      setBuildiumAccounts([]); setDashboardAccounts([]); setLinks([]); setFlags([]);
      setAddGlId(""); setAddAccountId("");
      return;
    }
    loadAll(selectedAssociation);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedAssociation]);

  const loadAll = async (assocId: string) => {
    setLoading(true);
    setAddGlId(""); setAddAccountId("");
    try {
      const [glRes, dashAccounts, linksRes, flagsRes] = await Promise.all([
        // Active Buildium accounts only — inactive ones just clutter the list
        invokeEdgeFunction("buildium-sync", { body: { syncType: "gl_accounts", selectedAssociationIds: [assocId] } }),
        fetchChartOfAccounts(assocId, "platform"),
        supabase.from("buildium_gl_account_links")
          .select("id, buildium_gl_id, buildium_name, buildium_number, buildium_type, account_id, is_push_target")
          .eq("association_id", assocId),
        supabase.from("buildium_unmapped_gl_accounts")
          .select("buildium_gl_id, buildium_name, buildium_number, buildium_type, 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);
      setLinks((linksRes.data || []) as LinkRow[]);
      setFlags((flagsRes.data || []) as FlagRow[]);
    } catch (err: any) {
      toast({ variant: "destructive", title: "Error loading GL account map", description: err.message });
    } finally {
      setLoading(false);
    }
  };

  const mappedGlIds = useMemo(() => new Set(links.map((l) => l.buildium_gl_id)), [links]);
  const accountById = useMemo(() => new Map(dashboardAccounts.map((a) => [a.id, a])), [dashboardAccounts]);
  const flaggedIds = useMemo(() => new Set(flags.map((f) => f.buildium_gl_id)), [flags]);

  // Buildium dropdown options: active unmapped accounts, plus any flagged
  // accounts the syncs hit that aren't in the active list (e.g. inactive in
  // Buildium but still referenced by history).
  const addOptions = useMemo(() => {
    const opts: BuildiumGLAccount[] = buildiumAccounts.filter((a) => !mappedGlIds.has(a.id));
    const have = new Set(opts.map((o) => o.id));
    for (const f of flags) {
      if (mappedGlIds.has(f.buildium_gl_id) || have.has(f.buildium_gl_id)) continue;
      opts.push({
        id: f.buildium_gl_id,
        name: `${f.buildium_name || `Buildium GL ${f.buildium_gl_id}`} (inactive)`,
        account_number: f.buildium_number,
        type: f.buildium_type || "",
      });
      have.add(f.buildium_gl_id);
    }
    return opts.sort((a, b) =>
      (a.account_number || "").localeCompare(b.account_number || "", undefined, { numeric: true }) || a.name.localeCompare(b.name));
  }, [buildiumAccounts, flags, mappedGlIds]);

  const sortedLinks = useMemo(
    () => [...links].sort((a, b) =>
      (a.buildium_number || "").localeCompare(b.buildium_number || "", undefined, { numeric: true })
      || (a.buildium_name || "").localeCompare(b.buildium_name || "")),
    [links],
  );

  const clearFlag = async (glId: string) => {
    await supabase.from("buildium_unmapped_gl_accounts").delete()
      .eq("association_id", selectedAssociation).eq("buildium_gl_id", glId);
    setFlags((prev) => prev.filter((f) => f.buildium_gl_id !== glId));
  };

  const insertLink = async (gl: BuildiumGLAccount, accountId: string): Promise<LinkRow | null> => {
    // Only one link per dashboard account can be the push target
    const hasPushTarget = links.some((l) => l.account_id === accountId && l.is_push_target);
    const { data, error } = await supabase
      .from("buildium_gl_account_links")
      .insert({
        association_id: selectedAssociation,
        buildium_gl_id: gl.id,
        buildium_name: gl.name.replace(/ \(inactive\)$/, ""),
        buildium_number: gl.account_number,
        buildium_type: gl.type || null,
        account_id: accountId,
        is_push_target: !hasPushTarget,
      })
      .select("id, buildium_gl_id, buildium_name, buildium_number, buildium_type, account_id, is_push_target")
      .single();
    if (error) {
      toast({ variant: "destructive", title: "Could not add mapping", description: error.message });
      return null;
    }
    return data as LinkRow;
  };

  const handleAdd = async () => {
    const gl = addOptions.find((o) => o.id === addGlId);
    if (!gl || !addAccountId) return;
    setBusy(true);
    try {
      const row = await insertLink(gl, addAccountId);
      if (row) {
        setLinks((prev) => [...prev, row]);
        await clearFlag(gl.id);
        setAddGlId(""); setAddAccountId("");
      }
    } finally {
      setBusy(false);
    }
  };

  const handleDelete = async (row: LinkRow) => {
    setBusy(true);
    try {
      const { error } = await supabase.from("buildium_gl_account_links").delete().eq("id", row.id);
      if (error) throw error;
      let next = links.filter((l) => l.id !== row.id);
      // If the removed row was the push target, promote a remaining sibling
      if (row.is_push_target) {
        const sibling = next.find((l) => l.account_id === row.account_id);
        if (sibling) {
          await supabase.from("buildium_gl_account_links").update({ is_push_target: true }).eq("id", sibling.id);
          next = next.map((l) => (l.id === sibling.id ? { ...l, is_push_target: true } : l));
        }
      }
      setLinks(next);
    } catch (err: any) {
      toast({ variant: "destructive", title: "Could not remove mapping", description: err.message });
    } finally {
      setBusy(false);
    }
  };

  const setPushTarget = async (row: LinkRow) => {
    setBusy(true);
    try {
      const current = links.find((l) => l.account_id === row.account_id && l.is_push_target && l.id !== row.id);
      if (current) {
        const { error } = await supabase.from("buildium_gl_account_links").update({ is_push_target: false }).eq("id", current.id);
        if (error) throw error;
      }
      const { error } = await supabase.from("buildium_gl_account_links").update({ is_push_target: true }).eq("id", row.id);
      if (error) throw error;
      setLinks((prev) => prev.map((l) =>
        l.id === row.id ? { ...l, is_push_target: true }
        : l.account_id === row.account_id ? { ...l, is_push_target: false }
        : l));
    } catch (err: any) {
      toast({ variant: "destructive", title: "Could not change push target", description: err.message });
    } finally {
      setBusy(false);
    }
  };

  // Auto-add exact code/name matches for unmapped active accounts
  const suggestMatches = async () => {
    setBusy(true);
    try {
      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 added = 0;
      let working = links;
      for (const gl of buildiumAccounts) {
        if (working.some((l) => l.buildium_gl_id === gl.id)) continue;
        const match = (gl.account_number ? byCode.get(normalize(gl.account_number)) : undefined) || byName.get(normalizeName(gl.name));
        if (!match) continue;
        const hasPushTarget = working.some((l) => l.account_id === match.id && l.is_push_target);
        const { data, error } = await supabase
          .from("buildium_gl_account_links")
          .insert({
            association_id: selectedAssociation,
            buildium_gl_id: gl.id,
            buildium_name: gl.name,
            buildium_number: gl.account_number,
            buildium_type: gl.type || null,
            account_id: match.id,
            is_push_target: !hasPushTarget,
          })
          .select("id, buildium_gl_id, buildium_name, buildium_number, buildium_type, account_id, is_push_target")
          .single();
        if (!error && data) {
          working = [...working, data as LinkRow];
          added++;
          if (flaggedIds.has(gl.id)) await clearFlag(gl.id);
        }
      }
      setLinks(working);
      toast({
        title: added > 0 ? `${added} mapping(s) added` : "No new matches found",
        description: added > 0 ? "Exact code/name matches were mapped automatically." : "Remaining accounts need to be added by hand.",
      });
    } finally {
      setBusy(false);
    }
  };

  // Dashboard accounts that already have several Buildium accounts mapped (for the push radio)
  const accountGroupSizes = useMemo(() => {
    const m = new Map<string, number>();
    for (const l of links) m.set(l.account_id, (m.get(l.account_id) ?? 0) + 1);
    return m;
  }, [links]);

  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">{links.length} mapped</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 — map them below
                  </p>
                  <div className="mt-2 flex flex-wrap gap-1.5">
                    {flags.map((f) => (
                      <button
                        key={f.buildium_gl_id}
                        type="button"
                        onClick={() => setAddGlId(f.buildium_gl_id)}
                        className="px-2 py-0.5 rounded border border-amber-300 bg-white text-xs text-amber-800 hover:bg-amber-100 transition-colors"
                        title="Select this account in the Buildium dropdown"
                      >
                        {glLabel(f)}
                      </button>
                    ))}
                  </div>
                </div>
              )}

              {/* Add a mapping: Buildium dropdown → system dropdown → Add */}
              <div className="border rounded-lg p-3 bg-muted/30">
                <p className="text-xs font-semibold uppercase text-muted-foreground mb-2">Add Mapping</p>
                <div className="flex flex-wrap items-center gap-2">
                  <Select value={addGlId || "__none__"} onValueChange={(v) => setAddGlId(v === "__none__" ? "" : v)}>
                    <SelectTrigger className="w-[320px] h-9 text-xs">
                      <SelectValue placeholder="Buildium account..." />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value="__none__">— Buildium account —</SelectItem>
                      {addOptions.map((gl) => (
                        <SelectItem key={gl.id} value={gl.id}>
                          {glLabel(gl)}{gl.type ? ` (${gl.type})` : ""}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                  <ArrowRight className="h-4 w-4 text-muted-foreground shrink-0" />
                  <Select value={addAccountId || "__none__"} onValueChange={(v) => setAddAccountId(v === "__none__" ? "" : v)}>
                    <SelectTrigger className="w-[320px] h-9 text-xs">
                      <SelectValue placeholder="System account..." />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value="__none__">— System account —</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>
                  <Button size="sm" className="gap-1 h-9" onClick={handleAdd} disabled={!addGlId || !addAccountId || busy}>
                    {busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Plus className="h-3.5 w-3.5" />} Add
                  </Button>
                  <Button variant="outline" size="sm" className="gap-1 h-9 ml-auto" onClick={suggestMatches} disabled={busy || dashboardAccounts.length === 0}>
                    <Wand2 className="h-3.5 w-3.5" /> Auto-map exact matches
                  </Button>
                </div>
              </div>

              {/* Existing mappings */}
              {sortedLinks.length === 0 ? (
                <p className="text-sm text-muted-foreground text-center py-4">No mappings yet — add the accounts you sync above.</p>
              ) : (
                <div className="border rounded-lg divide-y max-h-[480px] overflow-y-auto">
                  {sortedLinks.map((l) => {
                    const acct = accountById.get(l.account_id);
                    const showPushRadio = (accountGroupSizes.get(l.account_id) ?? 0) > 1;
                    return (
                      <div key={l.id} className="flex items-center gap-2 px-3 py-1.5 text-sm">
                        <span className="w-[42%] truncate">{glLabel(l)}</span>
                        <ArrowRight className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
                        <span className="flex-1 truncate text-muted-foreground">
                          {acct ? `${[acct.account_number, acct.account_name].filter(Boolean).join(" - ")}` : "Unknown account"}
                        </span>
                        {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-${l.account_id}`}
                              checked={l.is_push_target}
                              onChange={() => setPushTarget(l)}
                              disabled={busy}
                            />
                            push
                          </label>
                        )}
                        <Button
                          variant="ghost"
                          size="icon"
                          className="h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive"
                          onClick={() => handleDelete(l)}
                          disabled={busy}
                          title="Remove mapping"
                        >
                          <Trash2 className="h-3.5 w-3.5" />
                        </Button>
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          )
        )}
      </CardContent>
    </Card>
  );
}
