import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import {
  ArrowLeft,
  CheckCircle2,
  AlertOctagon,
  FolderOpen,
  RotateCcw,
  Home as HomeIcon,
} from "lucide-react";
import { useProfile } from "../contexts/ProfileContext";
import { useCategoryMigration } from "../hooks/useCategoryMigration";
import {
  computeMigrationPlan,
  type ProfileData,
} from "../services/categoryMappingService";
import {
  createPreMigrationBackup,
  BackupError,
} from "../services/categoryBackupService";
import { applyMigration } from "../services/categoryMigrationService";
import { getDb } from "../services/db";
import { getPreference } from "../services/userPreferenceService";
import StepDiscover from "../components/categories-migration/StepDiscover";
import StepSimulate from "../components/categories-migration/StepSimulate";
import StepConsent from "../components/categories-migration/StepConsent";

/**
 * 3-step category migration page — route `/settings/categories/migrate`.
 *
 * Wraps:
 *   - StepDiscover (read-only taxonomy preview, reused from #117)
 *   - StepSimulate (dry-run table + transaction preview side panel)
 *   - StepConsent  (checklist + loader + success/error screens)
 *
 * The page owns the data-fetching pipeline:
 *   1. Load profile data (categories, keywords, transactions, suppliers) from
 *      SQLite as soon as we enter step 2 the first time, then compute the
 *      MigrationPlan via the pure function from categoryMappingService.
 *   2. On consent-confirm: createPreMigrationBackup → applyMigration → outcome.
 *
 * State transitions are delegated to useCategoryMigration (useReducer).
 */

const CATEGORIES_SCHEMA_VERSION_KEY = "categories_schema_version";

type AlreadyMigratedState = "loading" | "needs_migration" | "already_v1";

export default function CategoriesMigrationPage() {
  const { t } = useTranslation();
  const { activeProfile } = useProfile();
  const {
    state,
    loadPlan,
    resolveRow,
    selectRow,
    goNext,
    goBack,
    startRun,
    setBackup,
    setOutcome,
    fail,
  } = useCategoryMigration();

  // Gate the page: profiles already on v1 should see a friendly "already done"
  // screen instead of re-migrating (re-running the writer would be idempotent
  // thanks to OR IGNORE, but it would surprise the user).
  const [schemaCheck, setSchemaCheck] = useState<AlreadyMigratedState>(
    "loading",
  );

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const version = await getPreference(CATEGORIES_SCHEMA_VERSION_KEY);
        if (cancelled) return;
        setSchemaCheck(version === "v1" ? "already_v1" : "needs_migration");
      } catch {
        if (!cancelled) setSchemaCheck("needs_migration");
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  // Lazy-load the profile data + plan the first time we enter the simulate step.
  const [txCountByV2Id, setTxCountByV2Id] = useState<Map<number, number>>(
    () => new Map(),
  );
  const [loadError, setLoadError] = useState<string | null>(null);
  const planLoadedRef = useRef(false);

  useEffect(() => {
    if (state.step !== "simulate" || planLoadedRef.current) return;
    planLoadedRef.current = true;

    (async () => {
      try {
        const db = await getDb();

        const [v2Cats, kws, txs, sups, txCounts] = await Promise.all([
          db.select<Array<{ id: number; name: string; parent_id: number | null }>>(
            "SELECT id, name, parent_id FROM categories WHERE is_active = 1",
          ),
          db.select<Array<{ id: number; keyword: string; category_id: number }>>(
            "SELECT id, keyword, category_id FROM keywords WHERE is_active = 1",
          ),
          db.select<
            Array<{
              id: number;
              description: string;
              category_id: number | null;
              supplier_id: number | null;
            }>
          >(
            "SELECT id, description, category_id, supplier_id FROM transactions",
          ),
          db.select<Array<{ id: number; name: string }>>(
            "SELECT id, name FROM suppliers WHERE is_active = 1",
          ),
          db.select<Array<{ category_id: number; cnt: number }>>(
            "SELECT category_id, COUNT(*) AS cnt FROM transactions WHERE category_id IS NOT NULL GROUP BY category_id",
          ),
        ]);

        const profileData: ProfileData = {
          v2Categories: v2Cats,
          keywords: kws,
          transactions: txs,
          suppliers: sups,
        };
        const plan = computeMigrationPlan(profileData);
        loadPlan(plan);

        const counts = new Map<number, number>();
        for (const row of txCounts) {
          counts.set(row.category_id, Number(row.cnt));
        }
        setTxCountByV2Id(counts);
      } catch (e) {
        const msg = e instanceof Error ? e.message : String(e);
        setLoadError(msg);
        planLoadedRef.current = false;
      }
    })();
  }, [state.step, loadPlan]);

  // Password for PIN-protected backups.
  const [password, setPassword] = useState("");
  const requiresPassword =
    !!activeProfile?.pin_hash && activeProfile.pin_hash.length > 0;

  // Loader stage for the 4 sub-steps (see StepConsent RunningLoader).
  const [runningStage, setRunningStage] = useState<0 | 1 | 2 | 3>(0);

  const handleConfirm = async () => {
    if (!activeProfile || !state.plan) return;
    startRun();
    setRunningStage(0);
    try {
      // 1. Create + verify the backup.
      const backup = await createPreMigrationBackup({
        profile: activeProfile,
        password: requiresPassword ? password : undefined,
      });
      setBackup(backup);
      setRunningStage(1);
      // 2. Tiny pause for the UI to breathe (checksum already verified inside
      //    createPreMigrationBackup, but we bump the stage for the loader).
      await new Promise((r) => setTimeout(r, 200));
      setRunningStage(2);
      // 3. Run the migration.
      const outcome = await applyMigration(state.plan, backup);
      setRunningStage(3);
      await new Promise((r) => setTimeout(r, 200));
      setOutcome(outcome);
    } catch (e) {
      if (e instanceof BackupError) {
        const label = t(
          `categoriesSeed.migration.error.backup.${e.code}`,
          { defaultValue: e.message },
        );
        fail(label);
      } else {
        const msg = e instanceof Error ? e.message : String(e);
        fail(msg);
      }
    }
  };

  // --------------------------------------------------------------------------
  // Renders
  // --------------------------------------------------------------------------

  if (schemaCheck === "loading") {
    return (
      <div className="flex items-center justify-center h-screen">
        <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[var(--primary)]" />
      </div>
    );
  }

  if (schemaCheck === "already_v1") {
    return (
      <div className="p-6 max-w-2xl mx-auto space-y-4">
        <Link
          to="/settings/data"
          className="inline-flex items-center gap-2 text-sm text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors"
        >
          <ArrowLeft size={16} />
          {t("categoriesSeed.migration.backToSettings")}
        </Link>
        <div className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-6 space-y-3">
          <h1 className="text-xl font-semibold">
            {t("categoriesSeed.migration.alreadyMigrated.title")}
          </h1>
          <p className="text-sm text-[var(--muted-foreground)]">
            {t("categoriesSeed.migration.alreadyMigrated.body")}
          </p>
        </div>
      </div>
    );
  }

  return (
    <div className="p-6 max-w-5xl mx-auto space-y-6">
      <div>
        <Link
          to="/settings/data"
          className="inline-flex items-center gap-2 text-sm text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors"
        >
          <ArrowLeft size={16} />
          {t("categoriesSeed.migration.backToSettings")}
        </Link>
      </div>

      <header className="space-y-1">
        <h1 className="text-2xl font-bold">
          {t("categoriesSeed.migration.pageTitle")}
        </h1>
        <p className="text-sm text-[var(--muted-foreground)]">
          {t("categoriesSeed.migration.pageSubtitle")}
        </p>
      </header>

      <Stepper step={state.step} />

      {loadError !== null && state.step === "simulate" && (
        <div className="rounded-xl border border-red-300 bg-red-50 p-4 text-sm text-red-900 dark:bg-red-900/10 dark:border-red-700 dark:text-red-200">
          {t("categoriesSeed.migration.simulate.loadError", {
            error: loadError,
          })}
        </div>
      )}

      {state.step === "discover" && <StepDiscover onNext={goNext} />}

      {state.step === "simulate" && state.plan !== null && (
        <StepSimulate
          plan={state.plan}
          unresolved={state.unresolved}
          selectedRowV2Id={state.selectedRowV2Id}
          transactionCountByV2Id={txCountByV2Id}
          onResolveRow={resolveRow}
          onSelectRow={selectRow}
          onNext={goNext}
          onBack={goBack}
        />
      )}

      {state.step === "simulate" && state.plan === null && loadError === null && (
        <div className="flex items-center justify-center p-10">
          <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[var(--primary)]" />
        </div>
      )}

      {(state.step === "consent" || state.step === "running") && (
        <StepConsent
          password={password}
          onPasswordChange={setPassword}
          requiresPassword={requiresPassword}
          isRunning={state.step === "running"}
          runningStage={runningStage}
          onBack={goBack}
          onConfirm={handleConfirm}
        />
      )}

      {state.step === "success" && state.outcome && (
        <SuccessScreen
          backupPath={state.outcome.backupPath}
          insertedV1={state.outcome.insertedV1Count}
          updatedTx={state.outcome.updatedTransactionsCount}
          updatedKw={state.outcome.updatedKeywordsCount}
          updatedBg={state.outcome.updatedBudgetsCount}
        />
      )}

      {state.step === "error" && (
        <ErrorScreen
          errors={state.errors}
          onRetry={goBack}
        />
      )}
    </div>
  );
}

interface StepperProps {
  step: string;
}
function Stepper({ step }: StepperProps) {
  const { t } = useTranslation();
  const steps: Array<{
    key: string;
    label: string;
    active: boolean;
    done: boolean;
  }> = useMemo(
    () => [
      {
        key: "discover",
        label: t("categoriesSeed.migration.stepper.discover"),
        active: step === "discover",
        done: ["simulate", "consent", "running", "success"].includes(step),
      },
      {
        key: "simulate",
        label: t("categoriesSeed.migration.stepper.simulate"),
        active: step === "simulate",
        done: ["consent", "running", "success"].includes(step),
      },
      {
        key: "consent",
        label: t("categoriesSeed.migration.stepper.consent"),
        active: step === "consent" || step === "running",
        done: step === "success",
      },
    ],
    [step, t],
  );
  return (
    <ol
      className="flex items-center gap-3 text-sm"
      aria-label={t("categoriesSeed.migration.stepper.ariaLabel")}
    >
      {steps.map((s, i) => (
        <li key={s.key} className="flex items-center gap-3">
          <span
            className={
              "inline-flex items-center justify-center w-7 h-7 rounded-full text-xs font-semibold border " +
              (s.done
                ? "bg-[var(--primary)] text-white border-[var(--primary)]"
                : s.active
                  ? "bg-[var(--primary)]/10 text-[var(--primary)] border-[var(--primary)]"
                  : "bg-[var(--muted)] text-[var(--muted-foreground)] border-[var(--border)]")
            }
          >
            {i + 1}
          </span>
          <span
            className={
              s.active
                ? "font-medium text-[var(--foreground)]"
                : s.done
                  ? "text-[var(--foreground)]"
                  : "text-[var(--muted-foreground)]"
            }
          >
            {s.label}
          </span>
          {i < steps.length - 1 && (
            <span
              aria-hidden="true"
              className="h-px w-8 bg-[var(--border)]"
            />
          )}
        </li>
      ))}
    </ol>
  );
}

interface SuccessScreenProps {
  backupPath: string;
  insertedV1: number;
  updatedTx: number;
  updatedKw: number;
  updatedBg: number;
}
function SuccessScreen({
  backupPath,
  insertedV1,
  updatedTx,
  updatedKw,
  updatedBg,
}: SuccessScreenProps) {
  const { t } = useTranslation();
  return (
    <section className="space-y-6">
      <div className="bg-[var(--card)] border border-green-300 dark:border-green-700 rounded-xl p-6 space-y-4">
        <div className="flex items-start gap-3">
          <CheckCircle2
            size={28}
            className="shrink-0 text-green-600 dark:text-green-400"
          />
          <div>
            <h2 className="text-xl font-semibold">
              {t("categoriesSeed.migration.success.title")}
            </h2>
            <p className="text-sm text-[var(--muted-foreground)]">
              {t("categoriesSeed.migration.success.subtitle")}
            </p>
          </div>
        </div>

        <div className="bg-[var(--muted)] rounded-lg p-4 space-y-2">
          <p className="text-sm font-medium text-[var(--foreground)]">
            <FolderOpen size={16} className="inline mr-1" />
            {t("categoriesSeed.migration.success.backupLabel")}
          </p>
          <code className="block text-xs break-all p-2 bg-[var(--background)] rounded border border-[var(--border)]">
            {backupPath}
          </code>
          <p className="text-xs text-[var(--muted-foreground)]">
            {t("categoriesSeed.migration.success.restoreHint")}
          </p>
        </div>

        <dl className="grid grid-cols-2 sm:grid-cols-4 gap-4 text-sm">
          <div>
            <dt className="text-xs uppercase text-[var(--muted-foreground)]">
              {t("categoriesSeed.migration.success.stats.inserted")}
            </dt>
            <dd className="text-lg font-semibold">{insertedV1}</dd>
          </div>
          <div>
            <dt className="text-xs uppercase text-[var(--muted-foreground)]">
              {t("categoriesSeed.migration.success.stats.transactions")}
            </dt>
            <dd className="text-lg font-semibold">{updatedTx}</dd>
          </div>
          <div>
            <dt className="text-xs uppercase text-[var(--muted-foreground)]">
              {t("categoriesSeed.migration.success.stats.keywords")}
            </dt>
            <dd className="text-lg font-semibold">{updatedKw}</dd>
          </div>
          <div>
            <dt className="text-xs uppercase text-[var(--muted-foreground)]">
              {t("categoriesSeed.migration.success.stats.budgets")}
            </dt>
            <dd className="text-lg font-semibold">{updatedBg}</dd>
          </div>
        </dl>
      </div>

      <div className="flex items-center justify-center gap-3">
        <Link
          to="/"
          className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-[var(--primary)] text-white hover:opacity-90"
        >
          <HomeIcon size={16} />
          {t("categoriesSeed.migration.success.backToDashboard")}
        </Link>
        <Link
          to="/categories"
          className="inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-[var(--border)] hover:bg-[var(--muted)]"
        >
          {t("categoriesSeed.migration.success.viewCategories")}
        </Link>
      </div>
    </section>
  );
}

interface ErrorScreenProps {
  errors: string[];
  onRetry: () => void;
}
function ErrorScreen({ errors, onRetry }: ErrorScreenProps) {
  const { t } = useTranslation();
  return (
    <section className="space-y-6">
      <div className="bg-[var(--card)] border border-red-300 dark:border-red-700 rounded-xl p-6 space-y-4">
        <div className="flex items-start gap-3">
          <AlertOctagon
            size={28}
            className="shrink-0 text-red-600 dark:text-red-400"
          />
          <div>
            <h2 className="text-xl font-semibold">
              {t("categoriesSeed.migration.error.title")}
            </h2>
            <p className="text-sm text-[var(--muted-foreground)]">
              {t("categoriesSeed.migration.error.subtitle")}
            </p>
          </div>
        </div>
        {errors.length > 0 && (
          <ul className="space-y-1 text-sm text-red-700 dark:text-red-400">
            {errors.map((e, i) => (
              <li
                key={i}
                className="p-2 rounded bg-red-50 dark:bg-red-900/10 break-words"
              >
                {e}
              </li>
            ))}
          </ul>
        )}
        <p className="text-xs text-[var(--muted-foreground)]">
          {t("categoriesSeed.migration.error.rollbackNote")}
        </p>
      </div>

      <div className="flex items-center justify-center gap-3">
        <button
          type="button"
          onClick={onRetry}
          className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-[var(--primary)] text-white hover:opacity-90"
        >
          <RotateCcw size={16} />
          {t("categoriesSeed.migration.error.retry")}
        </button>
        <Link
          to="/settings/data"
          className="inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-[var(--border)] hover:bg-[var(--muted)]"
        >
          {t("categoriesSeed.migration.error.backToSettings")}
        </Link>
      </div>
    </section>
  );
}
