"use client";

/**
 * 2026-11-11 — Companies → "🔀 Merge duplicates" (admins only).
 * Uses the same look-alike groups as the ⚠ DUPLICATE tag. In every group the REAL company is kept:
 * the one with certificates issued, then audits in the Audit Schedule, then audit requests / jobs.
 * Everything of the copies (audit requests, schedule rows, certificates, jobs, branches, portal users,
 * standards, NCs …) moves to the kept company, then the copies are deleted.
 * The older Merge page (/modules/companies/companies_f/merge) is unchanged.
 */
import React, { useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import toast from "react-hot-toast";
import {
  fixOrphans,
  getOrphans,
  getSmartGroups,
  smartMerge,
  type OrphanReport,
  type SmartGroup,
  type SmartStatus,
  type SmartTotals,
} from "@/lib/api/company-merge.api";

const C = { brand: "#760E9E", border: "#e6e8ee", muted: "#667085", text: "#101828" };

const STATUS: Record<SmartStatus, { label: string; bg: string; fg: string; hint: string }> = {
  clear: { label: "✓ Clear", bg: "#dcfce7", fg: "#166534", hint: "Only one copy has certificates / scheduled audits — that one is kept" },
  review: { label: "⚠ Review", bg: "#fef3c7", fg: "#92400e", hint: "Two or more copies have certificates or scheduled audits — check before merging (may be real branches)" },
  no_activity: { label: "○ No activity", bg: "#f2f4f7", fg: "#475467", hint: "No copy has a certificate or scheduled audit — the one with most records (or the oldest) is kept" },
};

const isBranchName = (n: string) => /\bbranch\b/i.test(n);

type Plan = { keep: number; remove: Set<number> };

export default function SmartMergeModal({ onClose, onChanged }: { onClose: () => void; onChanged?: () => void }) {
  const [groups, setGroups] = useState<SmartGroup[]>([]);
  const [totals, setTotals] = useState<SmartTotals | null>(null);
  const [loading, setLoading] = useState(true);
  const [busy, setBusy] = useState(false);
  const [progress, setProgress] = useState("");
  const [tab, setTab] = useState<SmartStatus | "all">("clear");
  const [q, setQ] = useState("");
  const [plans, setPlans] = useState<Record<string, Plan>>({});
  const [picked, setPicked] = useState<Set<string>>(new Set());
  const [open, setOpen] = useState<string | null>(null);
  const [orphans, setOrphans] = useState<OrphanReport | null>(null);

  const defaultPlan = (g: SmartGroup): Plan => ({
    keep: g.keeper_id,
    // branch names are NOT ticked for removal by default — they may be a real separate branch
    remove: new Set(g.companies.filter((c) => c.id !== g.keeper_id && !isBranchName(c.name)).map((c) => c.id)),
  });

  const load = () => {
    setLoading(true);
    getSmartGroups()
      .then((r) => {
        setGroups(r.groups);
        setTotals(r.totals);
        const p: Record<string, Plan> = {};
        r.groups.forEach((g) => (p[g.key] = defaultPlan(g)));
        setPlans(p);
        setPicked(new Set());
      })
      .catch((e) => toast.error(e?.message || "Could not load duplicate groups"))
      .finally(() => setLoading(false));
    getOrphans().then(setOrphans).catch(() => undefined);
  };
  useEffect(load, []);

  const shown = useMemo(() => {
    const s = q.trim().toLowerCase();
    return groups.filter(
      (g) => (tab === "all" || g.status === tab) && (!s || g.companies.some((c) => `${c.name} ${c.id} ${c.company_code ?? ""}`.toLowerCase().includes(s))),
    );
  }, [groups, tab, q]);

  const planOf = (g: SmartGroup) => plans[g.key] ?? defaultPlan(g);
  const setKeep = (g: SmartGroup, id: number) =>
    setPlans((p) => {
      const cur = planOf(g);
      const remove = new Set(cur.remove);
      remove.delete(id);
      if (cur.keep !== id && !isBranchName(g.companies.find((c) => c.id === cur.keep)?.name ?? "")) remove.add(cur.keep);
      return { ...p, [g.key]: { keep: id, remove } };
    });
  const toggleRemove = (g: SmartGroup, id: number) =>
    setPlans((p) => {
      const cur = planOf(g);
      const remove = new Set(cur.remove);
      if (remove.has(id)) remove.delete(id);
      else remove.add(id);
      return { ...p, [g.key]: { ...cur, remove } };
    });
  const togglePick = (key: string) =>
    setPicked((s) => {
      const n = new Set(s);
      if (n.has(key)) n.delete(key);
      else n.add(key);
      return n;
    });
  const allShownPicked = shown.length > 0 && shown.every((g) => picked.has(g.key));

  const pickedGroups = groups.filter((g) => picked.has(g.key) && planOf(g).remove.size > 0);
  const nRemove = pickedGroups.reduce((n, g) => n + planOf(g).remove.size, 0);

  const run = async (list: SmartGroup[]) => {
    const items = list.map((g) => ({ keep_id: planOf(g).keep, remove_ids: [...planOf(g).remove] })).filter((x) => x.remove_ids.length);
    if (!items.length) return toast.error("Nothing to merge — tick at least one group with a copy to remove");
    const n = items.reduce((s, x) => s + x.remove_ids.length, 0);
    const reviewN = list.filter((g) => g.status === "review").length;
    if (
      !window.confirm(
        `Merge ${n} duplicate compan${n === 1 ? "y" : "ies"} into ${items.length} kept compan${items.length === 1 ? "y" : "ies"}?\n\n` +
          `All audit requests, schedule rows, certificates, jobs, branches and portal users move to the kept company, ` +
          `then the copies are DELETED. This cannot be undone.` +
          (reviewN ? `\n\n⚠ ${reviewN} of these groups are marked "Review".` : ""),
      )
    )
      return;
    setBusy(true);
    let merged = 0;
    const failed: string[] = [];
    try {
      for (let i = 0; i < items.length; i += 25) {
        setProgress(`Merging… ${Math.min(i + 25, items.length)} / ${items.length} groups`);
        const r = await smartMerge(items.slice(i, i + 25));
        merged += r.merged;
        r.results.forEach((x) => x.failed.forEach((f) => failed.push(`#${f.id} → #${x.keep_id}: ${f.error}`)));
      }
      if (failed.length) toast.error(`${failed.length} merge(s) failed — first: ${failed[0]}`, { duration: 8000 });
      toast.success(`${merged} duplicate compan${merged === 1 ? "y" : "ies"} merged`);
      onChanged?.();
      load();
    } catch (e: any) {
      toast.error(e?.message || "Merge failed");
      load();
    } finally {
      setBusy(false);
      setProgress("");
    }
  };

  const doFixOrphans = async () => {
    if (!window.confirm("Re-link schedule rows, certificates and other records that older merges left pointing at a deleted company?")) return;
    setBusy(true);
    try {
      const r = await fixOrphans();
      const n = r.tables.reduce((s, t) => s + t.fixed, 0);
      toast.success(`${n} record(s) re-linked to the kept company`);
      setOrphans(await getOrphans());
      onChanged?.();
    } catch (e: any) {
      toast.error(e?.message || "Could not re-link");
    } finally {
      setBusy(false);
    }
  };
  const orphanFixable = orphans?.tables.reduce((s, t) => s + t.fixable, 0) ?? 0;

  const body = (
    <div
      onMouseDown={(e) => e.target === e.currentTarget && !busy && onClose()}
      style={{ position: "fixed", inset: 0, zIndex: 1000, background: "rgba(16,24,40,.45)", display: "flex", alignItems: "flex-start", justifyContent: "center", padding: "32px 16px", overflowY: "auto" }}
    >
      <div style={{ width: "min(1180px, 100%)", background: "#fff", borderRadius: 14, boxShadow: "0 24px 48px -12px rgba(16,24,40,.35)", overflow: "hidden" }}>
        {/* header */}
        <div style={{ padding: "16px 20px", borderBottom: `1px solid ${C.border}`, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
          <div>
            <h2 style={{ margin: 0, fontSize: 18, color: C.text }}>🔀 Merge duplicate companies</h2>
            <p style={{ margin: "2px 0 0", fontSize: 12.5, color: C.muted }}>
              The <b>real company</b> is kept — the one with <b>certificates issued</b>, then <b>audits in the Audit Schedule</b>. Everything of the copies moves to it, then the copies are deleted.
            </p>
          </div>
          <button type="button" onClick={onClose} disabled={busy} style={{ border: "none", background: "none", fontSize: 22, cursor: "pointer", color: C.muted }} aria-label="Close">
            ×
          </button>
        </div>

        {orphanFixable > 0 && (
          <div style={{ margin: "14px 20px 0", padding: "10px 12px", borderRadius: 10, background: "#fef2f2", border: "1px solid #fecaca", fontSize: 13, color: "#991b1b", display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
            <span style={{ flex: 1 }}>
              <b>{orphanFixable} record(s) lost their company in older merges</b> (
              {orphans!.tables.filter((t) => t.fixable).map((t) => `${t.fixable} ${t.table.replace(/_/g, " ")}`).join(", ")}). They can be re-linked to the company they were merged into.
            </span>
            <button type="button" disabled={busy} onClick={doFixOrphans} style={{ ...btnGhost, borderColor: "#fca5a5", color: "#991b1b" }}>
              Re-link {orphanFixable}
            </button>
          </div>
        )}

        {/* tabs + search */}
        <div style={{ padding: "14px 20px 10px", display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
          {(["clear", "review", "no_activity", "all"] as const).map((k) => {
            const n = k === "all" ? totals?.groups : totals?.[k];
            const on = tab === k;
            return (
              <button
                key={k}
                type="button"
                onClick={() => setTab(k)}
                title={k === "all" ? "Every duplicate group" : STATUS[k].hint}
                style={{ height: 32, padding: "0 12px", borderRadius: 999, fontSize: 12.5, fontWeight: 700, cursor: "pointer", border: `1px solid ${on ? C.brand : C.border}`, background: on ? C.brand : "#fff", color: on ? "#fff" : "#344054" }}
              >
                {k === "all" ? "All" : STATUS[k].label} {n !== undefined ? `(${n})` : ""}
              </button>
            );
          })}
          <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search company name / id" style={{ ...inp, marginLeft: "auto", width: 260 }} />
        </div>
        {tab !== "all" && <div style={{ padding: "0 20px 8px", fontSize: 12, color: C.muted }}>{STATUS[tab].hint}.</div>}

        {/* groups table */}
        <div style={{ margin: "0 20px", border: `1px solid ${C.border}`, borderRadius: 10, maxHeight: 520, overflow: "auto" }}>
          <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12.5 }}>
            <thead style={{ position: "sticky", top: 0, background: "#f9fafb", zIndex: 1 }}>
              <tr>
                <th style={th}>
                  <input
                    type="checkbox"
                    checked={allShownPicked}
                    onChange={() =>
                      setPicked((s) => {
                        const n = new Set(s);
                        shown.forEach((g) => (allShownPicked ? n.delete(g.key) : n.add(g.key)));
                        return n;
                      })
                    }
                  />
                </th>
                <th style={th}>Keep (real company)</th>
                <th style={th}>Why</th>
                <th style={th}>Copies to merge in</th>
                <th style={th}>Status</th>
                <th style={th}></th>
              </tr>
            </thead>
            <tbody>
              {loading ? (
                <tr>
                  <td colSpan={6} style={{ padding: 30, textAlign: "center", color: C.muted }}>Finding duplicates…</td>
                </tr>
              ) : shown.length === 0 ? (
                <tr>
                  <td colSpan={6} style={{ padding: 30, textAlign: "center", color: C.muted }}>No duplicate groups here.</td>
                </tr>
              ) : (
                shown.map((g) => {
                  const plan = planOf(g);
                  const keep = g.companies.find((c) => c.id === plan.keep) ?? g.companies[0];
                  const copies = g.companies.filter((c) => plan.remove.has(c.id));
                  const isOpen = open === g.key;
                  const st = STATUS[g.status];
                  return (
                    <React.Fragment key={g.key}>
                      <tr style={{ borderTop: "1px solid #f2f4f7", background: picked.has(g.key) ? "#faf5ff" : undefined }}>
                        <td style={td}>
                          <input type="checkbox" checked={picked.has(g.key)} onChange={() => togglePick(g.key)} />
                        </td>
                        <td style={{ ...td, fontWeight: 700, color: C.text }}>
                          {keep.name}
                          <div style={{ fontWeight: 400, fontSize: 11, color: C.muted }}>
                            #{keep.id} {keep.company_code ? `· ${keep.company_code}` : ""} {keep.contact_person ? `· ${keep.contact_person}` : ""}
                          </div>
                        </td>
                        <td style={td}>
                          <Stats c={keep} />
                        </td>
                        <td style={td}>
                          {copies.length === 0 ? (
                            <span style={{ color: "#98a2b3" }}>none ticked</span>
                          ) : (
                            copies.map((c) => (
                              <div key={c.id} style={{ fontSize: 12 }}>
                                <span style={{ color: "#b42318" }}>✕</span> {c.name} <span style={{ color: C.muted }}>#{c.id}</span>
                              </div>
                            ))
                          )}
                        </td>
                        <td style={td}>
                          <span title={st.hint} style={{ padding: "2px 8px", borderRadius: 999, background: st.bg, color: st.fg, fontWeight: 700, fontSize: 11.5, whiteSpace: "nowrap" }}>
                            {st.label}
                          </span>
                        </td>
                        <td style={{ ...td, whiteSpace: "nowrap" }}>
                          <button type="button" style={{ ...btnGhost, height: 28, fontSize: 12 }} onClick={() => setOpen(isOpen ? null : g.key)}>
                            {isOpen ? "Close" : "Review"}
                          </button>{" "}
                          <button
                            type="button"
                            disabled={busy || !plan.remove.size}
                            style={{ ...btnPrimary, height: 28, fontSize: 12, opacity: busy || !plan.remove.size ? 0.5 : 1 }}
                            onClick={() => run([g])}
                          >
                            Merge
                          </button>
                        </td>
                      </tr>
                      {isOpen && (
                        <tr>
                          <td colSpan={6} style={{ padding: "8px 14px 14px 40px", background: "#fcfcfd" }}>
                            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
                              <thead>
                                <tr>
                                  <th style={th}>Keep</th>
                                  <th style={th}>Merge in</th>
                                  <th style={th}>Company</th>
                                  <th style={th}>Certificates</th>
                                  <th style={th}>Scheduled audits</th>
                                  <th style={th}>Requests / jobs</th>
                                  <th style={th}>Contact</th>
                                </tr>
                              </thead>
                              <tbody>
                                {g.companies.map((c) => {
                                  const isKeep = c.id === plan.keep;
                                  return (
                                    <tr key={c.id} style={{ borderTop: "1px solid #f2f4f7", background: isKeep ? "#f0fdf4" : undefined }}>
                                      <td style={td}>
                                        <input type="radio" name={`keep-${g.key}`} checked={isKeep} onChange={() => setKeep(g, c.id)} />
                                      </td>
                                      <td style={td}>
                                        <input type="checkbox" disabled={isKeep} checked={plan.remove.has(c.id)} onChange={() => toggleRemove(g, c.id)} />
                                      </td>
                                      <td style={{ ...td, fontWeight: isKeep ? 700 : 500, color: C.text }}>
                                        {c.name}
                                        {isBranchName(c.name) && (
                                          <span style={{ marginLeft: 6, padding: "1px 6px", borderRadius: 999, background: "#e0f2fe", color: "#075985", fontSize: 10.5, fontWeight: 700 }}>BRANCH</span>
                                        )}
                                        <div style={{ fontWeight: 400, fontSize: 11, color: C.muted }}>
                                          #{c.id} {c.company_code ? `· ${c.company_code}` : ""} {c.city ? `· ${c.city}` : ""} {c.client_group ? `· ${c.client_group}` : ""}
                                        </div>
                                      </td>
                                      <td style={td}>{c.certificates ? <b style={{ color: "#047857" }}>{c.certificates}{c.active_certificates ? ` (${c.active_certificates} active)` : ""}</b> : "—"}</td>
                                      <td style={td}>{c.schedules_all ? <b style={{ color: "#1d4ed8" }}>{c.schedules}{c.schedules_all > c.schedules ? ` (+${c.schedules_all - c.schedules} cancelled)` : ""}</b> : "—"}</td>
                                      <td style={td}>{c.audit_requests || c.jobs ? `${c.audit_requests} / ${c.jobs}` : "—"}</td>
                                      <td style={{ ...td, fontSize: 11.5 }}>
                                        {[c.contact_person, c.mobile, c.email].filter(Boolean).join(" · ") || <span style={{ color: "#98a2b3" }}>no real contact</span>}
                                      </td>
                                    </tr>
                                  );
                                })}
                              </tbody>
                            </table>
                            <div style={{ fontSize: 11.5, color: C.muted, marginTop: 6 }}>
                              Pick the company to keep with ◉. Untick a copy to leave it as a separate company (e.g. a real branch). The kept company keeps its own name; empty contact fields are filled from the copies.
                            </div>
                          </td>
                        </tr>
                      )}
                    </React.Fragment>
                  );
                })
              )}
            </tbody>
          </table>
        </div>

        {/* action bar */}
        <div style={{ padding: "12px 20px 18px", display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
          <span style={{ fontSize: 12.5, color: C.muted, flex: 1 }}>
            {progress || (picked.size ? `${picked.size} group(s) ticked → ${nRemove} duplicate(s) will be merged.` : "Tick groups, or use Merge on one row. Start with the ✓ Clear tab — those are safe.")}
          </span>
          <button type="button" disabled={busy || !nRemove} onClick={() => run(pickedGroups)} style={{ ...btnPrimary, opacity: busy || !nRemove ? 0.55 : 1 }}>
            {busy ? "Working…" : `Merge ${nRemove} duplicate${nRemove === 1 ? "" : "s"} in ${pickedGroups.length} group${pickedGroups.length === 1 ? "" : "s"}`}
          </button>
        </div>
      </div>
    </div>
  );
  return typeof document !== "undefined" ? createPortal(body, document.body) : body;
}

function Stats({ c }: { c: { certificates: number; schedules: number; audit_requests: number; jobs: number } }) {
  const chip = (txt: string, bg: string, fg: string) => (
    <span style={{ display: "inline-block", margin: "0 4px 2px 0", padding: "1px 7px", borderRadius: 999, background: bg, color: fg, fontWeight: 700, fontSize: 11 }}>{txt}</span>
  );
  const out: React.ReactNode[] = [];
  if (c.certificates) out.push(<React.Fragment key="c">{chip(`🎓 ${c.certificates} cert`, "#dcfce7", "#166534")}</React.Fragment>);
  if (c.schedules) out.push(<React.Fragment key="s">{chip(`📅 ${c.schedules} scheduled`, "#dbeafe", "#1e40af")}</React.Fragment>);
  if (c.audit_requests) out.push(<React.Fragment key="r">{chip(`${c.audit_requests} request`, "#f2f4f7", "#344054")}</React.Fragment>);
  if (c.jobs) out.push(<React.Fragment key="j">{chip(`${c.jobs} job`, "#f2f4f7", "#344054")}</React.Fragment>);
  return out.length ? <>{out}</> : <span style={{ color: "#98a2b3", fontSize: 11.5 }}>no activity — oldest record</span>;
}

const inp: React.CSSProperties = { height: 34, padding: "0 10px", border: "1px solid #d0d5dd", borderRadius: 8, fontSize: 13, background: "#fff", color: "#101828" };
const th: React.CSSProperties = { textAlign: "left", padding: "8px 10px", fontSize: 11, fontWeight: 700, color: "#475467", textTransform: "uppercase", letterSpacing: ".03em", borderBottom: "1px solid #e6e8ee", whiteSpace: "nowrap" };
const td: React.CSSProperties = { padding: "7px 10px", verticalAlign: "top", color: "#344054" };
const btnPrimary: React.CSSProperties = { height: 36, padding: "0 14px", border: "none", borderRadius: 8, background: "#760E9E", color: "#fff", fontWeight: 700, fontSize: 13, cursor: "pointer" };
const btnGhost: React.CSSProperties = { height: 36, padding: "0 12px", border: "1px solid #d0d5dd", borderRadius: 8, background: "#fff", color: "#344054", fontWeight: 600, fontSize: 13, cursor: "pointer" };
