"use client";

import { useMemo, useState } from "react";
import type {
  ApplyResponse,
  DiffPair,
  DiffStats,
  DiffTemplate,
} from "@/lib/api/types";
import {
  DEFAULT_PAIR_CHOICE,
  buildPairKey,
  compileSelections,
  type PairChoice,
  type SelectionMap,
} from "@/lib/api/report-templates";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Dialog } from "@/components/ui/dialog";
import { useToast } from "@/components/ui/toast";
import { IconAlertTriangle, IconCheckCircle } from "@/components/icons";
import { PairRow } from "./pair-row";

// Calque visuel ligne pour ligne de coolcare/admin/report_templates_review.php
// + report_templates_review.js. Couleurs hex en dur (slate-800, slate-700,
// gray-50, …) car coolcare n'a pas de dark mode et le user veut une copie
// fidèle. Le reste du panel reste theme-aware.

type ApplyState =
  | { phase: "idle" }
  | { phase: "preparing-dry" }
  | { phase: "dry-result"; data: ApplyResponse["data"] }
  | { phase: "committing" }
  | { phase: "committed"; data: ApplyResponse["data"] }
  | { phase: "error"; message: string };

type ServerErrorBody = {
  success: false;
  message?: string;
  error?: string;
  error_code?: string;
  expected_sha256?: string;
  actual_sha256?: string;
};

// Ordre + libellés coolcare pour la stats line.
const STATS_KEYS: (keyof DiffStats)[] = [
  "common",
  "common_divergent",
  "rename",
  "xlsx_only",
  "db_only_orphan",
  "db_only_volontaire",
];

export function ApplyFlow({
  template,
  file,
}: {
  template: DiffTemplate;
  file: File;
}) {
  const { toast } = useToast();
  const [selections, setSelections] = useState<SelectionMap>({});
  const [state, setState] = useState<ApplyState>({ phase: "idle" });

  const phases = template.phases ?? [];
  const stats = template.stats;

  function effectiveChoice(pair: DiffPair): PairChoice {
    if (pair.category === "db_only_volontaire") return "db";
    return selections[pair.pair_uid] ?? DEFAULT_PAIR_CHOICE[pair.category];
  }

  function setChoice(uid: string, choice: PairChoice) {
    setSelections((prev) => ({ ...prev, [uid]: choice }));
  }

  // Stats line "common=2 · common_divergent=61 · rename=3 · xlsx_only=7"
  // (calque exact de coolcare/renderTemplate). On filtre les zéros.
  const statsLine = useMemo(() => {
    if (!stats) return "—";
    const parts: string[] = [];
    for (const k of STATS_KEYS) {
      const v = stats[k];
      if (v && v > 0) parts.push(`${k}=${v}`);
    }
    return parts.length ? parts.join(" · ") : "—";
  }, [stats]);

  const canApply = template.db_template_id !== null && phases.length > 0;
  const applying =
    state.phase === "preparing-dry" || state.phase === "committing";

  async function postApply(dryRun: boolean) {
    if (!template.db_template_id) return null;
    const compiled = compileSelections(template, selections);
    const form = new FormData();
    form.append("xlsx_file", file);
    form.append("template_id", String(template.db_template_id));
    form.append("sheet_name", template.sheet_name);
    form.append("expected_xlsx_sha256", template.xlsx_sha256);
    form.append("diff_template", JSON.stringify({ phases }));
    form.append("selections", JSON.stringify(compiled));
    form.append("dry_run", dryRun ? "1" : "0");
    const res = await fetch("/api/report-templates/apply", {
      method: "POST",
      body: form,
    });
    const body = (await res.json().catch(() => null)) as
      | ApplyResponse
      | ServerErrorBody
      | null;
    return { res, body };
  }

  async function handleDryRun() {
    setState({ phase: "preparing-dry" });
    try {
      const r = await postApply(true);
      if (!r) {
        setState({
          phase: "error",
          message: "Préparation impossible — db_template_id manquant.",
        });
        return;
      }
      const { res, body } = r;
      if (!res.ok || !body || (body as ServerErrorBody).success === false) {
        setState({
          phase: "error",
          message: formatApplyError(body, res.status),
        });
        return;
      }
      setState({ phase: "dry-result", data: (body as ApplyResponse).data });
    } catch (e) {
      setState({
        phase: "error",
        message: e instanceof Error ? e.message : "Erreur réseau",
      });
    }
  }

  async function handleCommit() {
    setState({ phase: "committing" });
    try {
      const r = await postApply(false);
      if (!r) return;
      const { res, body } = r;
      if (!res.ok || !body || (body as ServerErrorBody).success === false) {
        setState({
          phase: "error",
          message: formatApplyError(body, res.status),
        });
        return;
      }
      const data = (body as ApplyResponse).data;
      setState({ phase: "committed", data });
      toast({
        title: "Template appliqué",
        description: `version ${data.new_version_label}`,
        tone: "success",
        duration: 4000,
      });
    } catch (e) {
      setState({
        phase: "error",
        message: e instanceof Error ? e.message : "Erreur réseau",
      });
    }
  }

  return (
    <>
      <section className="overflow-hidden rounded-lg border border-[#e5e7eb] bg-white">
        {/* Header card-head : slate-800 fixe (pas thème) — calque coolcare */}
        <header className="flex flex-wrap items-center gap-2 bg-[#1f2937] px-4 py-[0.65rem] text-white">
          <span className="text-[0.9rem] font-semibold">
            📄 {template.sheet_name}
          </span>
          {template.machine_type_value && (
            <span className="text-[0.78rem] opacity-85">
              → {template.machine_type_value}
            </span>
          )}
          <span className="text-[0.78rem] opacity-85">
            | BDD{" "}
            {template.db_template_id !== null ? (
              <>
                id={template.db_template_id} ({template.db_name ?? ""})
              </>
            ) : (
              <span className="text-[#fca5a5]">
                aucun template BDD trouvé — TOUT serait créé
              </span>
            )}
          </span>
          <span className="text-[0.72rem] text-[#cbd5e1]">{statsLine}</span>
          <span className="ml-auto">
            {canApply ? (
              <button
                type="button"
                onClick={handleDryRun}
                disabled={applying}
                className="cursor-pointer rounded-md border-0 bg-[#16a34a] px-[0.8rem] py-[0.35rem] text-[0.8rem] font-bold text-white transition-colors hover:bg-[#15803d] disabled:cursor-not-allowed disabled:opacity-50"
              >
                {state.phase === "preparing-dry"
                  ? "⏳ Calcul…"
                  : "🚀 Apply ce template"}
              </button>
            ) : (
              <span className="text-[0.7rem] text-[#9ca3af]">
                (création initiale non encore supportée par l&apos;apply)
              </span>
            )}
          </span>
        </header>

        {template.error && (
          <div className="flex items-start gap-2 border-b border-[#fde68a] bg-[#fffbeb] px-4 py-2 text-[0.78rem] text-[#92400e]">
            <IconAlertTriangle size={12} className="mt-0.5 shrink-0" />
            <span>{template.error}</span>
          </div>
        )}

        {phases.length === 0 ? (
          <div className="px-5 py-6 text-center text-sm italic text-[#9ca3af]">
            Aucune phase à afficher.
          </div>
        ) : (
          phases.map((phase) => (
            <div key={phase.index} className="border-t border-dashed border-[#e5e7eb]">
              {/* Phase header : slate-700 fixe, NON-sticky, 3 cols 78px/1fr/1fr */}
              <div className="grid grid-cols-[78px_1fr_1fr] bg-[#374151] text-white">
                <div className="border-r border-[#475569] px-4 py-2 text-[0.8rem]">
                  Phase {phase.index}
                </div>
                <div className="border-r border-[#475569] px-4 py-2 text-[0.8rem]">
                  {phase.xlsx_name || (
                    <span className="opacity-50">—</span>
                  )}
                </div>
                <div className="px-4 py-2 text-[0.8rem]">
                  {phase.db_name || <span className="opacity-50">—</span>}
                </div>
              </div>
              {(phase.pairs ?? []).map((pair) => {
                const key = buildPairKey(template.sheet_name, pair);
                return (
                  <div key={pair.pair_uid}>
                    <PairRow
                      pair={pair}
                      pairKey={key}
                      selectedChoice={effectiveChoice(pair)}
                      onChoiceChange={setChoice}
                    />
                    {(pair.children ?? []).map((child) => {
                      const childKey = buildPairKey(
                        template.sheet_name,
                        child,
                      );
                      return (
                        <PairRow
                          key={child.pair_uid}
                          pair={child}
                          isChild
                          pairKey={childKey}
                          selectedChoice={effectiveChoice(child)}
                          onChoiceChange={setChoice}
                        />
                      );
                    })}
                  </div>
                );
              })}
            </div>
          ))
        )}
      </section>

      <DryRunDialog
        open={state.phase === "dry-result"}
        onClose={() => setState({ phase: "idle" })}
        data={state.phase === "dry-result" ? state.data : undefined}
        onCommit={handleCommit}
        committing={state.phase === "committing"}
      />

      <CommittedDialog
        open={state.phase === "committed"}
        onClose={() => setState({ phase: "idle" })}
        data={state.phase === "committed" ? state.data : undefined}
      />

      <Dialog
        open={state.phase === "error"}
        onOpenChange={(o) => !o && setState({ phase: "idle" })}
        title="Erreur"
        size="md"
      >
        {state.phase === "error" && (
          <div className="flex items-start gap-3">
            <div className="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-danger-subtle text-danger ring-1 ring-red-200">
              <IconAlertTriangle size={16} />
            </div>
            <p className="text-sm text-foreground break-words">
              {state.message}
            </p>
          </div>
        )}
      </Dialog>
    </>
  );
}

function DryRunDialog({
  open,
  onClose,
  data,
  onCommit,
  committing,
}: {
  open: boolean;
  onClose: () => void;
  data?: ApplyResponse["data"];
  onCommit: () => void | Promise<void>;
  committing: boolean;
}) {
  if (!data) {
    return (
      <Dialog
        open={open}
        onOpenChange={(o) => !o && onClose()}
        title="Dry-run"
        size="lg"
      />
    );
  }
  const ops = data.operations ?? [];
  const cmp = data.comparison ?? { xlsx: [], db_before: [], db_after: [] };

  return (
    <Dialog
      open={open}
      onOpenChange={(o) => !o && onClose()}
      title="Dry-run — aucune écriture"
      description={`apply_run_id : ${data.apply_run_id} · version cible : ${data.new_version_label}`}
      size="xl"
      footer={
        <>
          <Button variant="ghost" onClick={onClose} type="button">
            Annuler
          </Button>
          <Button onClick={onCommit} type="button" loading={committing}>
            Confirmer le commit
          </Button>
        </>
      }
    >
      <div className="space-y-4">
        <SummaryGrid summary={data.summary} />

        <section>
          <h4 className="mb-1.5 text-xs font-medium uppercase tracking-wider text-muted-foreground">
            Opérations ({ops.length})
          </h4>
          {ops.length === 0 ? (
            <p className="text-xs text-muted-foreground">
              Aucune opération nécessaire.
            </p>
          ) : (
            <div className="rounded-md border border-border overflow-hidden bg-surface">
              <ul className="divide-y divide-border max-h-48 overflow-y-auto">
                {ops.map((op, i) => (
                  <li
                    key={i}
                    className="flex items-center gap-3 px-3 py-1.5 text-xs text-foreground"
                  >
                    <Badge tone={opTone(op.operation)}>{op.operation}</Badge>
                    <span className="text-muted-foreground tabular-nums">
                      {op.step_id ? `step #${op.step_id}` : ""}
                      {op.option_id ? ` · option #${op.option_id}` : ""}
                    </span>
                  </li>
                ))}
              </ul>
            </div>
          )}
        </section>

        <section>
          <h4 className="mb-1.5 text-xs font-medium uppercase tracking-wider text-muted-foreground">
            Comparaison
          </h4>
          <div className="grid gap-2 lg:grid-cols-3">
            <ComparisonColumn
              title="XLSX"
              tone="success"
              items={cmp.xlsx as Array<Record<string, unknown>>}
            />
            <ComparisonColumn
              title="BDD avant"
              tone="neutral"
              items={cmp.db_before as Array<Record<string, unknown>>}
            />
            <ComparisonColumn
              title="BDD après"
              tone="accent"
              items={cmp.db_after as Array<Record<string, unknown>>}
            />
          </div>
        </section>
      </div>
    </Dialog>
  );
}

function CommittedDialog({
  open,
  onClose,
  data,
}: {
  open: boolean;
  onClose: () => void;
  data?: ApplyResponse["data"];
}) {
  if (!data) {
    return <Dialog open={open} onOpenChange={(o) => !o && onClose()} />;
  }
  return (
    <Dialog
      open={open}
      onOpenChange={(o) => !o && onClose()}
      title="Application terminée"
      description={`version ${data.new_version_label} · ${data.apply_run_id}`}
      size="lg"
      footer={
        <Button onClick={onClose} type="button">
          Fermer
        </Button>
      }
    >
      <div className="space-y-4">
        <div className="flex items-start gap-3 rounded-md border border-green-200 bg-success-subtle px-3 py-2 dark:border-green-900">
          <IconCheckCircle size={16} className="mt-0.5 text-success" />
          <p className="text-sm text-foreground">
            Le template a été appliqué en base. Snapshot pré-apply enregistré
            dans <code className="font-mono">report_templates_backup</code>.
          </p>
        </div>
        <SummaryGrid summary={data.summary} />
      </div>
    </Dialog>
  );
}

function SummaryGrid({
  summary,
}: {
  summary: ApplyResponse["data"]["summary"];
}) {
  const items: { label: string; value: number }[] = [
    { label: "Steps insérés", value: summary.steps_inserted },
    { label: "Steps soft-deleted", value: summary.steps_soft_deleted },
    { label: "Options insérées", value: summary.options_inserted },
    { label: "Options soft-deleted", value: summary.options_soft_deleted },
    { label: "Pairs traitées", value: summary.pairs_processed },
    { label: "Pairs no-op", value: summary.pairs_noop },
  ];
  return (
    <dl className="grid grid-cols-2 gap-2 sm:grid-cols-3">
      {items.map((it) => (
        <div
          key={it.label}
          className="rounded-md border border-border bg-surface-2 px-3 py-2"
        >
          <dt className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
            {it.label}
          </dt>
          <dd className="mt-0.5 text-lg font-semibold tabular-nums text-foreground">
            {it.value}
          </dd>
        </div>
      ))}
    </dl>
  );
}

function ComparisonColumn({
  title,
  tone,
  items,
}: {
  title: string;
  tone: "success" | "neutral" | "accent";
  items: Array<Record<string, unknown>>;
}) {
  return (
    <div className="rounded-md border border-border overflow-hidden flex flex-col bg-surface">
      <div className="border-b border-border bg-surface-2 px-3 py-1.5">
        <Badge tone={tone}>{title}</Badge>
        <span className="ml-2 text-xs text-muted-foreground tabular-nums">
          {items.length}
        </span>
      </div>
      <ul className="divide-y divide-border max-h-64 overflow-y-auto">
        {items.length === 0 ? (
          <li className="px-3 py-2 text-xs text-muted-foreground">vide</li>
        ) : (
          items.map((it, i) => (
            <li
              key={i}
              className="px-3 py-1 text-[11px] font-mono text-foreground truncate"
            >
              {extractLabel(it)}
            </li>
          ))
        )}
      </ul>
    </div>
  );
}

function extractLabel(o: Record<string, unknown>): string {
  const item = typeof o.item === "string" ? o.item : null;
  const phase = typeof o.phase === "string" ? o.phase : null;
  if (item) return phase ? `${phase} · ${item}` : item;
  return JSON.stringify(o).slice(0, 80);
}

function opTone(
  op: string,
): "success" | "danger" | "info" | "warning" | "neutral" | "accent" {
  if (op.startsWith("insert")) return "success";
  if (op.includes("delete")) return "danger";
  if (op.includes("bump")) return "accent";
  if (op.includes("update")) return "warning";
  return "neutral";
}

function formatApplyError(
  body: ApplyResponse | ServerErrorBody | null,
  status: number,
): string {
  if (!body) return `HTTP ${status}`;
  const err = body as ServerErrorBody;
  if (err.error_code === "SHA256_MISMATCH") {
    return `Le fichier XLSX a changé entre le diff et l'apply. Re-uploade-le et recalcule le diff.`;
  }
  if (err.error_code === "WORKFLOWS_IN_PROGRESS") {
    return "Des workflows d'intervention sont en cours sur ce template.";
  }
  if (err.error_code === "CROSS_TEMPLATE_PARENT") {
    return "Un step de ce template est référencé par un autre template (parent_step_id).";
  }
  if (err.error_code === "MIGRATION_ID_LOCKED") {
    return "Un step ou un descendant porte un migration_id qui le verrouille en soft-delete.";
  }
  if (err.error_code === "INVALID_SELECTION_PAIR") {
    return "Une pair a une sélection invalide (ni XLSX ni BDD, ou les deux).";
  }
  if (err.error_code === "PAYLOAD_INVALID") {
    return err.message ?? "Payload invalide.";
  }
  return err.message ?? err.error ?? `HTTP ${status}`;
}
