import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";
import { AlertTriangle, FolderOpen, Loader2, X } from "lucide-react";
import {
  backupFileExists,
  isFileEncrypted,
  pickBackupFile,
  restoreFromBackup,
  RestoreError,
  type RestorableMigrationJournal,
} from "../../services/categoryRestoreService";

// -----------------------------------------------------------------------------
// Confirmation modal used by both the in-window banner and the permanent
// Settings entry to roll back a category migration. Mounted via a portal on
// document.body so it escapes any ancestor stacking context.
//
// High-level flow:
//   1. Mount with the recorded `journal` — show the date + path and two CTAs.
//   2. On "Restore" click: verify the recorded file still exists on disk. If
//      not, flip to the "missing file" state and let the user pick a file.
//   3. If the file is SREF-encrypted, prompt the PIN; otherwise proceed.
//   4. Run `restoreFromBackup`, reload the app on success so every page sees
//      the restored state.
//
// The UX intentionally keeps destructive action behind an explicit "Restore"
// button — a single click does not restore; the user must read the warning
// and click again. The banner and the permanent Settings entry both route
// to this same component.
// -----------------------------------------------------------------------------

type Phase =
  | "confirm" // initial screen with the data/path + Restore/Cancel buttons
  | "missing" // recorded path no longer on disk — user must pick a file
  | "password" // detected SREF encryption — ask for the PIN
  | "restoring" // in-flight restore call
  | "error"; // failed — surface the error and allow retry

interface CategoriesMigrationRestoreModalProps {
  journal: RestorableMigrationJournal;
  onClose: () => void;
  /**
   * Called after the restore finishes successfully. By default, the app is
   * reloaded to refresh all data-bound views. Consumers may override this
   * (e.g. in tests) but the default is the safe path.
   */
  onRestored?: () => void;
}

export default function CategoriesMigrationRestoreModal({
  journal,
  onClose,
  onRestored,
}: CategoriesMigrationRestoreModalProps) {
  const { t } = useTranslation();
  const [phase, setPhase] = useState<Phase>("confirm");
  const [effectivePath, setEffectivePath] = useState<string>(journal.backupPath);
  const [password, setPassword] = useState<string>("");
  const [error, setError] = useState<string | null>(null);
  const [isEncrypted, setIsEncrypted] = useState<boolean>(false);

  // Close on Escape (disabled while restoring to avoid interrupting the wipe).
  useEffect(() => {
    function handleEscape(e: KeyboardEvent) {
      if (e.key === "Escape" && phase !== "restoring") onClose();
    }
    document.addEventListener("keydown", handleEscape);
    return () => document.removeEventListener("keydown", handleEscape);
  }, [onClose, phase]);

  const formattedDate = formatJournalDate(journal.timestamp);

  // Centralised error mapping — keeps the rendered messages i18n-driven.
  function messageForError(e: unknown): string {
    if (e instanceof RestoreError) {
      return t(
        `settings.categoriesCard.restoreModal.errors.${e.code}`,
        t("settings.categoriesCard.restoreModal.errors.read_failed"),
      );
    }
    return e instanceof Error ? e.message : String(e);
  }

  async function beginRestore(pathToUse: string) {
    setError(null);

    // 1. Verify the file is still on disk.
    const exists = await backupFileExists(pathToUse);
    if (!exists) {
      setPhase("missing");
      return;
    }

    // 2. If it is SREF-encrypted, branch to the password prompt.
    let encrypted = false;
    try {
      encrypted = await isFileEncrypted(pathToUse);
    } catch {
      // If we cannot even inspect the file, treat it as unreadable.
      setPhase("error");
      setError(t("settings.categoriesCard.restoreModal.errors.read_failed"));
      return;
    }

    setIsEncrypted(encrypted);
    setEffectivePath(pathToUse);

    if (encrypted) {
      setPhase("password");
      return;
    }

    await runRestore(pathToUse, null);
  }

  async function runRestore(pathToUse: string, pw: string | null) {
    setPhase("restoring");
    setError(null);
    try {
      await restoreFromBackup(pathToUse, pw);
      if (onRestored) {
        onRestored();
      } else {
        // Full reload so every page (dashboard, transactions, etc.) reflects
        // the restored v2 state instead of stale v1 derived data.
        window.location.reload();
      }
    } catch (e) {
      setError(messageForError(e));
      setPhase("error");
    }
  }

  async function handlePickFile() {
    setError(null);
    try {
      const picked = await pickBackupFile();
      if (!picked) {
        // User cancelled; stay in the "missing" state so they can retry.
        return;
      }
      // Reset password between attempts on different files.
      setPassword("");
      await beginRestore(picked);
    } catch (e) {
      setError(messageForError(e));
      setPhase("error");
    }
  }

  function handlePasswordSubmit(e: React.FormEvent) {
    e.preventDefault();
    void runRestore(effectivePath, password.length > 0 ? password : null);
  }

  const isBusy = phase === "restoring";

  return createPortal(
    <div
      className="fixed inset-0 z-[200] flex items-center justify-center bg-black/50 p-4"
      onClick={(e) => {
        if (e.target === e.currentTarget && !isBusy) onClose();
      }}
      role="dialog"
      aria-modal="true"
      aria-labelledby="restore-modal-title"
    >
      <div className="bg-[var(--card)] rounded-xl border border-[var(--border)] shadow-2xl w-full max-w-lg">
        {/* Header */}
        <div className="flex items-start justify-between px-6 py-4 border-b border-[var(--border)]">
          <div className="flex items-start gap-3">
            <div className="mt-0.5 shrink-0 rounded-lg bg-[var(--negative)]/10 p-2 text-[var(--negative)]">
              <AlertTriangle size={18} />
            </div>
            <div>
              <h2 id="restore-modal-title" className="text-lg font-semibold">
                {t("settings.categoriesCard.restoreModal.title")}
              </h2>
              <p className="text-sm text-[var(--muted-foreground)] mt-0.5">
                {t("settings.categoriesCard.restoreModal.subtitle", {
                  date: formattedDate,
                })}
              </p>
            </div>
          </div>
          <button
            type="button"
            onClick={onClose}
            disabled={isBusy}
            aria-label={t("settings.categoriesCard.restoreModal.close")}
            className="shrink-0 rounded-md p-1 text-[var(--muted-foreground)] hover:bg-[var(--muted)] hover:text-[var(--foreground)] transition-colors disabled:opacity-50"
          >
            <X size={18} />
          </button>
        </div>

        {/* Body */}
        <div className="px-6 py-4 space-y-4">
          {/* Destructive action warning — kept visible across every phase */}
          <p className="text-sm text-[var(--foreground)]">
            {t("settings.categoriesCard.restoreModal.warning")}
          </p>

          {phase === "confirm" && (
            <div className="rounded-lg bg-[var(--muted)] px-3 py-2 text-xs text-[var(--muted-foreground)] break-all">
              <p className="font-medium text-[var(--foreground)] mb-1">
                {t("settings.categoriesCard.restoreModal.filePath")}
              </p>
              <code>{journal.backupPath}</code>
            </div>
          )}

          {phase === "missing" && (
            <div className="rounded-lg border border-[var(--negative)]/30 bg-[var(--negative)]/5 px-3 py-3 text-sm text-[var(--foreground)] space-y-2">
              <p className="font-medium text-[var(--negative)]">
                {t("settings.categoriesCard.restoreModal.fileMissingTitle")}
              </p>
              <p className="text-[var(--muted-foreground)] text-xs break-all">
                {journal.backupPath}
              </p>
              <p className="text-xs text-[var(--muted-foreground)]">
                {t("settings.categoriesCard.restoreModal.fileMissingHint")}
              </p>
            </div>
          )}

          {phase === "password" && (
            <form onSubmit={handlePasswordSubmit} className="space-y-3">
              <label className="block text-sm font-medium">
                {t("settings.categoriesCard.restoreModal.passwordLabel")}
              </label>
              <input
                type="password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                autoFocus
                className="w-full rounded-lg border border-[var(--border)] bg-[var(--background)] px-3 py-2 text-sm focus:border-[var(--primary)] focus:outline-none"
              />
              <p className="text-xs text-[var(--muted-foreground)]">
                {t("settings.categoriesCard.restoreModal.passwordHelp")}
              </p>
            </form>
          )}

          {phase === "restoring" && (
            <div className="flex items-center gap-2 text-sm text-[var(--muted-foreground)]">
              <Loader2 size={16} className="animate-spin" />
              {t("settings.categoriesCard.restoreModal.restoring")}
            </div>
          )}

          {phase === "error" && error && (
            <div className="rounded-lg border border-[var(--negative)]/30 bg-[var(--negative)]/5 px-3 py-3 text-sm text-[var(--negative)]">
              {error}
            </div>
          )}
        </div>

        {/* Footer */}
        <div className="flex items-center justify-end gap-2 px-6 py-4 border-t border-[var(--border)]">
          {phase === "confirm" && (
            <>
              <button
                type="button"
                onClick={onClose}
                className="px-4 py-2 text-sm rounded-lg border border-[var(--border)] hover:bg-[var(--muted)] transition-colors"
              >
                {t("settings.categoriesCard.restoreModal.cancel")}
              </button>
              <button
                type="button"
                onClick={() => beginRestore(journal.backupPath)}
                className="px-4 py-2 text-sm font-medium rounded-lg bg-[var(--negative)] text-white hover:opacity-90 transition-opacity"
              >
                {t("settings.categoriesCard.restoreModal.confirm")}
              </button>
            </>
          )}

          {phase === "missing" && (
            <>
              <button
                type="button"
                onClick={onClose}
                className="px-4 py-2 text-sm rounded-lg border border-[var(--border)] hover:bg-[var(--muted)] transition-colors"
              >
                {t("settings.categoriesCard.restoreModal.cancel")}
              </button>
              <button
                type="button"
                onClick={handlePickFile}
                className="flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg bg-[var(--primary)] text-white hover:opacity-90 transition-opacity"
              >
                <FolderOpen size={16} />
                {t("settings.categoriesCard.restoreModal.pickFile")}
              </button>
            </>
          )}

          {phase === "password" && (
            <>
              <button
                type="button"
                onClick={onClose}
                className="px-4 py-2 text-sm rounded-lg border border-[var(--border)] hover:bg-[var(--muted)] transition-colors"
              >
                {t("settings.categoriesCard.restoreModal.cancel")}
              </button>
              <button
                type="button"
                onClick={() =>
                  runRestore(effectivePath, password.length > 0 ? password : null)
                }
                disabled={password.length === 0}
                className="px-4 py-2 text-sm font-medium rounded-lg bg-[var(--negative)] text-white hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
              >
                {t("settings.categoriesCard.restoreModal.confirm")}
              </button>
            </>
          )}

          {phase === "error" && (
            <>
              <button
                type="button"
                onClick={onClose}
                className="px-4 py-2 text-sm rounded-lg border border-[var(--border)] hover:bg-[var(--muted)] transition-colors"
              >
                {t("settings.categoriesCard.restoreModal.close")}
              </button>
              <button
                type="button"
                onClick={() =>
                  isEncrypted
                    ? setPhase("password")
                    : void beginRestore(effectivePath)
                }
                className="px-4 py-2 text-sm font-medium rounded-lg bg-[var(--primary)] text-white hover:opacity-90 transition-opacity"
              >
                {t("settings.categoriesCard.restoreModal.retry")}
              </button>
            </>
          )}
        </div>
      </div>
    </div>,
    document.body,
  );
}

/**
 * Format the backup ISO timestamp as a human-readable local date+time.
 * Falls back to the raw string on invalid input.
 */
function formatJournalDate(iso: string): string {
  const ts = Date.parse(iso);
  if (Number.isNaN(ts)) return iso;
  return new Date(ts).toLocaleString();
}
