"use client";

import { useEffect, useMemo, useState } from "react";
import { ArrowLeft, ArrowRight, CalendarClock, Pencil, Plus, Trash2, X } from "lucide-react";
import { InfoBubble } from "@/components/InfoBubble";
import { Button, useConfirm, useToast } from "@/components/ui";
import { api } from "@/lib/api-client";
import { computePlan } from "@/lib/calculations";
import { CATEGORY_LABELS, CATEGORY_ORDER } from "@/lib/elements";
import { formatChf } from "@/lib/format";
import { actualKindOf, resolveActuals, toPlanYear, type ActualsSetInput } from "@/lib/actuals";
import { resolveRootElementId } from "@/lib/montecarlo";
import type { ElementCategory } from "@/lib/elements";
import type { PlanInput } from "@/lib/types";

interface StoredSet extends ActualsSetInput {
  author: string;
}

// Ein Eingabefeld im Wizard -- je Wurzel-Element eines oder (bei Immobilien) zwei.
interface Row {
  rootId: string;
  name: string;
  category: ElementCategory;
  scenarioNames: string[];
  planValue: number; // Vorbelegung aus dem Basisszenario für das gewählte Jahr
  planMortgage?: number;
  kind: ReturnType<typeof actualKindOf>;
}

const dt = (iso: string) =>
  new Date(`${iso}T00:00:00Z`).toLocaleDateString("de-CH", { day: "2-digit", month: "long", year: "numeric" });

interface LoadedScenario {
  id: string;
  name: string;
  isBase: boolean;
  plan: PlanInput;
}

export function ActualsDialog({
  planId,
  planName,
  scenarioMetas,
  initial,
  embedded = false,
  onClose,
  onChanged,
}: {
  planId: string;
  planName: string;
  // Alle Szenarien dieses Plans (nur Kopfdaten) -- die Pläne werden hier nachgeladen.
  scenarioMetas: { id: string; name: string; isBase: boolean }[];
  // Das bereits geöffnete Szenario, damit der Dialog sofort etwas anzeigen kann.
  initial: LoadedScenario;
  // Eingebettet in den Hauptbereich (Plan-Ansicht) statt als Pop-up -- ohne Overlay,
  // ohne Backdrop, ohne Schliessen-Knopf.
  embedded?: boolean;
  onClose: () => void;
  onChanged: () => void;
}) {
  const [loaded, setLoaded] = useState<Record<string, LoadedScenario>>(() => ({ [initial.id]: initial }));

  // Die übrigen Szenarien nachladen: Ein Ist-Satz gilt für ALLE, also müssen auch Elemente
  // erscheinen, die es nur in einem Nebenszenario gibt.
  useEffect(() => {
    const missing = scenarioMetas.filter((m) => m.id !== initial.id);
    if (missing.length === 0) return;
    let cancelled = false;
    (async () => {
      try {
        const entries = await Promise.all(
          missing.map(async (m) => {
            const data = await api.get<{ plan: PlanInput }>(`/api/scenarios/${m.id}`);
            return [m.id, { id: m.id, name: m.name, isBase: m.isBase, plan: data.plan }] as const;
          })
        );
        if (!cancelled) setLoaded((prev) => ({ ...prev, ...Object.fromEntries(entries) }));
      } catch {
        // Fehlende Nebenszenarien sind verschmerzbar -- der Wizard zeigt dann weniger Zeilen.
      }
    })();
    return () => {
      cancelled = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [scenarioMetas.map((m) => m.id).join(","), initial.id]);

  const scenarios = useMemo(
    () => scenarioMetas.map((m) => loaded[m.id]).filter((s): s is LoadedScenario => !!s),
    [scenarioMetas, loaded]
  );
  const [sets, setSets] = useState<StoredSet[] | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [mode, setMode] = useState<"list" | "wizard">("list");
  const [step, setStep] = useState<1 | 2>(1);
  const [busy, setBusy] = useState(false);
  const confirm = useConfirm();
  const toast = useToast();

  // Schritt 1
  const [recordedOn, setRecordedOn] = useState(() => new Date().toISOString().slice(0, 10));
  const [comment, setComment] = useState("");
  // Schritt 2
  const [values, setValues] = useState<Record<string, { value?: number; mortgage?: number }>>({});
  const [cash, setCash] = useState<number>(0);
  // null = neuer Datensatz; sonst die id des gerade bearbeiteten (dann PUT statt POST).
  const [editingId, setEditingId] = useState<string | null>(null);

  const base = scenarios.find((s) => s.isBase) ?? scenarios[0];
  const year = Number(recordedOn.slice(0, 4));

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const data = await api.get<{ sets: StoredSet[] }>(`/api/plans/${planId}/actuals`);
        if (!cancelled) setSets(data.sets);
      } catch (e) {
        if (!cancelled) setError(e instanceof Error ? e.message : "Konnte nicht geladen werden.");
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [planId]);

  // Herkunft aller Elemente über alle Szenarien -- Grundlage der Wurzel-Auflösung.
  const origins = useMemo(
    () => scenarios.flatMap((s) => s.plan.elements.map((e) => ({ id: e.id, sourceElementId: e.sourceElementId ?? null }))),
    [scenarios]
  );

  // Alle Elemente ALLER Szenarien, zusammengefasst auf ihre Wurzel. Vorbelegt mit dem
  // berechneten Stand des Basisszenarios im gewählten Jahr; fehlt das Element dort, wird es
  // aus dem erstbesten Szenario geholt, das es kennt.
  const rows: Row[] = useMemo(() => {
    const sourceById = new Map(origins.map((o) => [o.id, o.sourceElementId]));
    const byRoot = new Map<string, Row>();

    const ordered = [base, ...scenarios.filter((s) => s.id !== base?.id)].filter(Boolean);
    for (const sc of ordered) {
      const planYear = toPlanYear(year, sc.plan.startYear);
      const computed = computePlan(sc.plan);
      for (const el of sc.plan.elements) {
        const kind = actualKindOf(el.category);
        const root = resolveRootElementId(el.id, sourceById);

        // Jahresstand aus dem Verlauf: genau der Wert, den der Plan für dieses Jahr vorsieht.
        const yearly = computed.phases
          .flatMap((p) => p.elements.filter((e) => e.elementId === el.id).flatMap((e) => e.yearly))
          .find((y) => y.year === planYear);

        // Die AHV ist nur erfassbar, wenn die Rente zum Stichtag bereits läuft -- vorher gibt
        // es keinen Stand, den man ablesen könnte.
        if (el.category === "AHV" && !(yearly && yearly.value > 0)) continue;

        const existing = byRoot.get(root);
        if (existing) {
          if (!existing.scenarioNames.includes(sc.name)) existing.scenarioNames.push(sc.name);
          continue;
        }
        byRoot.set(root, {
          rootId: root,
          name: el.name,
          category: el.category,
          scenarioNames: [sc.name],
          // Bei einer Immobilie ist `yearly.value` das EIGENKAPITAL (Verkehrswert − Hypothek).
          // Erfasst und gerechnet wird aber der VERKEHRSWERT -- er steht in `propertyValue`.
          // Wurde hier bis 0.31 das Eigenkapital vorbelegt, setzte der Rechenkern es als
          // Verkehrswert ein, während die Hypothek stehen blieb: Das Eigenkapital brach im
          // Ist-Jahr schlagartig ein (typischerweise ins Negative).
          planValue:
            kind === "PROPERTY"
              ? Math.round(yearly?.propertyValue ?? 0)
              : Math.round(Math.abs(yearly?.value ?? 0)),
          planMortgage: kind === "PROPERTY" ? Math.round(yearly?.mortgage ?? 0) : undefined,
          kind,
        });
      }
    }

    return [...byRoot.values()].sort((a, b) => {
      const ca = CATEGORY_ORDER.indexOf(a.category);
      const cb = CATEGORY_ORDER.indexOf(b.category);
      return ca !== cb ? ca - cb : a.name.localeCompare(b.name, "de-CH");
    });
  }, [scenarios, base, origins, year]);

  // Der geplante Cash-Bestand im gewählten Jahr -- Vorbelegung für das Cash-Feld.
  const planCash = useMemo(() => {
    if (!base) return 0;
    const planYear = toPlanYear(year, base.plan.startYear);
    if (planYear === null) return 0;
    // Der Stand am Ende GENAU DIESES Jahres -- nicht am Ende der Phase. In einer Phase über
    // zehn Jahre, in der sich das Cash-Konto füllt, sind das zwei völlig verschiedene Zahlen.
    const computed = computePlan(base.plan);
    return computed.yearly.find((y) => y.year === planYear)?.cash ?? 0;
  }, [base, year]);

  function startWizard() {
    setEditingId(null);
    setValues({});
    setCash(planCash);
    setComment("");
    setRecordedOn(new Date().toISOString().slice(0, 10));
    setStep(1);
    setMode("wizard");
  }

  // Bestehenden Datensatz zum Bearbeiten oeffnen. Die gespeicherten Werte sind bereits auf
  // WURZEL-Element-IDs erfasst -- genau die Form, mit der der Wizard arbeitet.
  function startEdit(set: StoredSet) {
    setEditingId(set.id);
    setRecordedOn(set.recordedOn);
    setComment(set.comment ?? "");
    setValues({ ...set.values });
    setCash(typeof set.cash === "number" ? set.cash : 0);
    setStep(1);
    setMode("wizard");
  }

  // Beim Wechsel auf Schritt 2 mit den Planwerten vorbelegen -- der Nutzer überschreibt nur,
  // was tatsächlich abweicht.
  function goToStep2() {
    // Beim Bearbeiten stehen die erfassten Werte bereits -- sie duerfen nicht durch die
    // Planwerte ersetzt werden. Nur Zeilen, fuer die nichts erfasst ist (z. B. ein spaeter
    // hinzugekommenes Element), werden ergaenzt.
    const prefill: Record<string, { value?: number; mortgage?: number }> = {};
    for (const r of rows) {
      if (editingId && values[r.rootId]) {
        prefill[r.rootId] = values[r.rootId];
        continue;
      }
      prefill[r.rootId] =
        r.kind === "PROPERTY" ? { value: r.planValue, mortgage: r.planMortgage ?? 0 } : { value: r.planValue };
    }
    setValues(prefill);
    if (!editingId) setCash(planCash);
    setStep(2);
  }

  async function reload() {
    const data = await api.get<{ sets: StoredSet[] }>(`/api/plans/${planId}/actuals`);
    setSets(data.sets);
  }

  async function save() {
    setBusy(true);
    try {
      const payload = { recordedOn, comment: comment.trim() || undefined, cash, values };
      if (editingId) {
        await api.put(`/api/plans/${planId}/actuals/${editingId}`, payload);
      } else {
        await api.post(`/api/plans/${planId}/actuals`, payload);
      }
      toast("success", `Effektive Werte für ${dt(recordedOn)} ${editingId ? "aktualisiert" : "erfasst"}.`);
      await reload();
      setEditingId(null);
      setMode("list");
      onChanged();
    } catch (e) {
      toast("error", e instanceof Error ? e.message : "Speichern fehlgeschlagen.");
    } finally {
      setBusy(false);
    }
  }

  async function remove(set: StoredSet) {
    const ok = await confirm({
      title: "Datensatz löschen?",
      message: `Die effektiven Werte vom ${dt(set.recordedOn)} werden entfernt. Der Plan selbst bleibt unverändert.`,
      confirmLabel: "Löschen",
      danger: true,
    });
    if (!ok) return;
    try {
      await api.delete(`/api/plans/${planId}/actuals/${set.id}`);
      await reload();
      onChanged();
      toast("success", "Datensatz gelöscht.");
    } catch (e) {
      toast("error", e instanceof Error ? e.message : "Löschen fehlgeschlagen.");
    }
  }

  // Wie viele Elemente weichen ab? Kleine Orientierungshilfe in der Liste.
  const deviationCount = (set: ActualsSetInput) => {
    const resolved = resolveActuals([set], base?.plan ?? scenarios[0].plan, origins);
    return resolved.length === 0 ? 0 : Object.keys(resolved[0].byElementId).length;
  };

  const setRow = (rootId: string, patch: { value?: number; mortgage?: number }) =>
    setValues((prev) => ({ ...prev, [rootId]: { ...prev[rootId], ...patch } }));

  const body = (
    <>
        <div className="flex items-start justify-between gap-3">
          <div>
            <h2 className="flex items-center gap-2 text-base font-semibold text-fg">
              <CalendarClock className="h-5 w-5 text-accent" /> Effektive Werte
            </h2>
            <p className="mt-0.5 text-xs text-muted">
              Plan «{planName}». Was tatsächlich eingetreten ist – der Plan selbst bleibt unverändert.
            </p>
          </div>
          {!embedded && (
            <button type="button" onClick={onClose} aria-label="Schliessen" className="rounded-md p-1 text-faint hover:bg-surface-2">
              <X className="h-4 w-4" />
            </button>
          )}
        </div>

        {mode === "list" && (
          <>
            <div className="rounded-xl border border-border bg-surface-2 p-3 text-xs leading-relaxed text-muted">
              Ein Datensatz hält fest, wie es an einem Stichtag <strong className="text-fg">wirklich</strong> aussah.
              Die Berechnung läuft dann ein zweites Mal: gleiche Mechanik, aber ab jedem erfassten Jahr mit den
              echten Zahlen. Werte, die du weglässt, laufen unverändert auf ihrer Planlinie weiter.
              Ein Datensatz gilt für <strong className="text-fg">alle Szenarien</strong> dieses Plans – die
              Wirklichkeit ist dieselbe, egal wogegen man sie hält.
            </div>

            {error && <p className="text-xs text-danger">{error}</p>}
            {!sets && !error && <p className="text-xs text-muted">Wird geladen…</p>}

            {sets && sets.length === 0 && (
              <p className="rounded-lg border border-dashed border-border bg-surface-2 p-4 text-xs text-muted">
                Noch keine effektiven Werte erfasst.
              </p>
            )}

            {sets && sets.length > 0 && (
              <div className="flex flex-col gap-2">
                {sets.map((s, i) => (
                  <div
                    key={s.id}
                    onClick={() => startEdit(s)}
                    title="Zum Bearbeiten anklicken"
                    className={`flex cursor-pointer flex-wrap items-center gap-3 rounded-xl border p-3 transition-colors hover:border-accent ${
                      i === 0 ? "border-accent bg-accent-soft/20" : "border-border bg-surface-2"
                    }`}
                  >
                    <div className="min-w-0 flex-1">
                      <div className="flex items-center gap-2 text-sm font-medium text-fg">
                        {dt(s.recordedOn)}
                        {i === 0 && (
                          <span className="rounded bg-accent px-1.5 py-0.5 text-[10px] font-semibold text-accent-fg">
                            aktuellster
                          </span>
                        )}
                      </div>
                      <div className="text-[11px] text-muted">
                        rechnet ab {s.year} · {deviationCount(s)} Werte · {s.author}
                        {typeof s.cash === "number" && ` · Cash ${formatChf(s.cash)}`}
                      </div>
                      {s.comment && <div className="mt-0.5 truncate text-xs text-fg">«{s.comment}»</div>}
                    </div>
                    <button
                      type="button"
                      onClick={(e) => {
                        e.stopPropagation();
                        startEdit(s);
                      }}
                      className="flex items-center gap-1 rounded-lg border border-border px-2 py-1 text-xs text-muted hover:bg-surface-2 hover:text-fg"
                    >
                      <Pencil className="h-3.5 w-3.5" /> Bearbeiten
                    </button>
                    <button
                      type="button"
                      onClick={(e) => {
                        e.stopPropagation();
                        void remove(s);
                      }}
                      className="flex items-center gap-1 rounded-lg border border-border px-2 py-1 text-xs text-muted hover:border-danger hover:bg-danger-soft hover:text-danger"
                    >
                      <Trash2 className="h-3.5 w-3.5" /> Löschen
                    </button>
                  </div>
                ))}
              </div>
            )}

            <div>
              <Button onClick={startWizard}>
                <Plus className="h-4 w-4" /> Effektive Werte erfassen
              </Button>
            </div>
          </>
        )}

        {mode === "wizard" && step === 1 && (
          <>
            <div className="text-xs font-semibold uppercase tracking-wide text-faint">
              {editingId ? "Bearbeiten" : "Erfassen"} · Schritt 1 von 2 · Stichtag
            </div>
            <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
              <div>
                <label className="mb-1 flex items-center text-xs font-medium text-muted">
                  Datum der Erfassung
                  <InfoBubble text="Das exakte Datum erscheint in der Liste und auf der Zeitachse. Für die Berechnung zählt nur die Jahreszahl – der Rechenkern arbeitet in ganzen Jahren ab Planbeginn." />
                </label>
                <input
                  type="date"
                  value={recordedOn}
                  onChange={(e) => setRecordedOn(e.target.value)}
                  className="w-full rounded-lg border border-border bg-surface px-2.5 py-1.5 text-sm text-fg"
                />
              </div>
              <div>
                <label className="mb-1 block text-xs font-medium text-muted">Notiz (optional)</label>
                <input
                  value={comment}
                  onChange={(e) => setComment(e.target.value)}
                  placeholder="z. B. «nach Jahresabschluss»"
                  className="w-full rounded-lg border border-border bg-surface px-2.5 py-1.5 text-sm text-fg"
                />
              </div>
            </div>
            <p className="rounded-lg border border-border bg-surface-2 px-3 py-2 text-xs text-muted">
              Gerechnet wird ab dem Jahr <strong className="text-fg">{year}</strong>. Die Vorbelegung im nächsten
              Schritt zeigt, was dein Plan für dieses Jahr vorsieht – du überschreibst nur, was tatsächlich anders ist.
            </p>
            <div className="flex items-center gap-2">
              <Button variant="secondary" onClick={() => setMode("list")}>
                <ArrowLeft className="h-4 w-4" /> Zurück
              </Button>
              <Button onClick={goToStep2}>
                Weiter <ArrowRight className="h-4 w-4" />
              </Button>
            </div>
          </>
        )}

        {mode === "wizard" && step === 2 && (
          <>
            <div className="text-xs font-semibold uppercase tracking-wide text-faint">
              {editingId ? "Bearbeiten" : "Erfassen"} · Schritt 2 von 2 · Werte per {dt(recordedOn)}
            </div>

            <div className="max-h-[52vh] overflow-y-auto rounded-xl border border-border">
              <table className="w-full border-collapse text-sm">
                <thead className="sticky top-0 z-10 bg-surface-2">
                  <tr className="text-xs text-faint">
                    <th className="px-3 py-2 text-left font-semibold">Element</th>
                    <th className="px-3 py-2 text-right font-semibold">Laut Plan {year}</th>
                    <th className="px-3 py-2 text-right font-semibold">Effektiv</th>
                  </tr>
                </thead>
                <tbody>
                  <tr className="border-t border-border bg-surface-2/60">
                    <td colSpan={3} className="px-3 py-1 text-[11px] font-semibold uppercase tracking-wide text-faint">
                      Cash
                    </td>
                  </tr>
                  <tr className="border-t border-border">
                    <td className="px-3 py-2 font-medium text-fg">Cash-Konto</td>
                    <td className="px-3 py-2 text-right text-muted">{formatChf(planCash)}</td>
                    <td className="px-3 py-2">
                      <NumInput value={cash} onChange={setCash} />
                    </td>
                  </tr>

                  {rows.map((r, i) => {
                    const header = i === 0 || rows[i - 1].category !== r.category ? r.category : null;
                    const v = values[r.rootId] ?? {};
                    return (
                      <FragmentRow
                        key={r.rootId}
                        header={header}
                        row={r}
                        value={v}
                        onChange={(patch) => setRow(r.rootId, patch)}
                      />
                    );
                  })}
                </tbody>
              </table>
            </div>

            <p className="text-[11px] text-faint">
              Einkommen und Ausgaben bitte als <strong>Jahresbetrag</strong> erfassen (nominal, wie tatsächlich
              geflossen). Bei Immobilien zählt der Verkehrswert und die Restschuld getrennt. Was du auf dem
              Planwert stehen lässt, wird als «keine Abweichung» gewertet.
            </p>

            <div className="flex items-center gap-2">
              <Button variant="secondary" onClick={() => setStep(1)}>
                <ArrowLeft className="h-4 w-4" /> Zurück
              </Button>
              <Button disabled={busy} onClick={save}>
                {busy ? "Speichern…" : "Speichern"}
              </Button>
            </div>
          </>
        )}
    </>
  );

  // Eingebettet: schlichter Container im Hauptbereich. Sonst: Pop-up mit Overlay.
  if (embedded) {
    return <div className="flex flex-col gap-4">{body}</div>;
  }
  return (
    <div className="ui-fade fixed inset-0 z-40 flex items-start justify-center overflow-y-auto bg-black/40 px-4 py-8" onClick={onClose}>
      <div onClick={(e) => e.stopPropagation()} className="ui-pop flex w-full max-w-4xl flex-col gap-4 rounded-2xl border border-border bg-surface p-6 shadow-xl">
        {body}
      </div>
    </div>
  );
}

function FragmentRow({
  header,
  row,
  value,
  onChange,
}: {
  header: ElementCategory | null;
  row: Row;
  value: { value?: number; mortgage?: number };
  onChange: (patch: { value?: number; mortgage?: number }) => void;
}) {
  return (
    <>
      {header && (
        <tr className="border-t border-border bg-surface-2/60">
          <td colSpan={3} className="px-3 py-1 text-[11px] font-semibold uppercase tracking-wide text-faint">
            {CATEGORY_LABELS[header]}
          </td>
        </tr>
      )}
      <tr className="border-t border-border">
        <td className="px-3 py-2">
          <div className="font-medium text-fg">{row.name}</div>
          {row.scenarioNames.length > 1 && (
            <div className="text-[10px] text-faint">gilt für {row.scenarioNames.join(", ")}</div>
          )}
          {row.kind === "FLOW" && <div className="text-[10px] text-faint">Jahresbetrag</div>}
          {/* Klarstellen, dass hier der Verkehrswert steht -- nicht das Eigenkapital, das die
              Matrix zeigt. */}
          {row.kind === "PROPERTY" && <div className="text-[10px] text-faint">Verkehrswert + Restschuld</div>}
        </td>
        <td className="px-3 py-2 text-right text-muted">
          {formatChf(row.planValue)}
          {row.kind === "PROPERTY" && (
            <div className="text-[11px] text-faint">Hypothek {formatChf(row.planMortgage ?? 0)}</div>
          )}
        </td>
        <td className="px-3 py-2">
          <NumInput value={value.value ?? 0} onChange={(v) => onChange({ value: v })} />
          {row.kind === "PROPERTY" && (
            <div className="mt-1">
              <NumInput
                value={value.mortgage ?? 0}
                onChange={(v) => onChange({ mortgage: v })}
                label="Hypothek"
              />
            </div>
          )}
        </td>
      </tr>
    </>
  );
}

function NumInput({ value, onChange, label }: { value: number; onChange: (v: number) => void; label?: string }) {
  return (
    <div className="flex items-center justify-end gap-1">
      {label && <span className="text-[10px] text-faint">{label}</span>}
      <input
        type="number"
        value={Number.isFinite(value) ? value : 0}
        onChange={(e) => onChange(Math.round(Number(e.target.value) || 0))}
        className="w-32 rounded border border-border bg-surface px-2 py-1 text-right text-sm text-fg"
      />
    </div>
  );
}

