import { describe, it, expect } from "vitest";
import { migrationReducer, INITIAL_STATE } from "./useCategoryMigration";
import type {
  MigrationPlan,
  MappingRow,
} from "../services/categoryMappingService";
import type { BackupResult } from "../services/categoryBackupService";
import type { MigrationOutcome } from "../services/categoryMigrationService";

// ---------------------------------------------------------------------------
// Fixture helpers
// ---------------------------------------------------------------------------

function makeRow(v2: number, v1: number | null): MappingRow {
  return {
    v2CategoryId: v2,
    v2CategoryName: `v2-${v2}`,
    v1TargetId: v1,
    v1TargetName: v1 === null ? null : `v1-${v1}`,
    confidence: v1 === null ? "none" : "high",
    reason: v1 === null ? "review" : "keyword",
  };
}

function makePlan(rows: MappingRow[], preserved: MappingRow[] = []): MigrationPlan {
  const unresolved = rows.filter((r) => r.v1TargetId === null);
  return {
    rows,
    preserved,
    unresolved,
    stats: {
      total: rows.length,
      high: rows.filter((r) => r.confidence === "high").length,
      medium: rows.filter((r) => r.confidence === "medium").length,
      low: rows.filter((r) => r.confidence === "low").length,
      none: unresolved.length,
    },
  };
}

const FAKE_BACKUP: BackupResult = {
  path: "/tmp/backup.sref",
  size: 1024,
  checksum: "abc",
  verifiedAt: new Date().toISOString(),
  encrypted: false,
};

const FAKE_OUTCOME: MigrationOutcome = {
  succeeded: true,
  insertedV1Count: 139,
  updatedTransactionsCount: 100,
  updatedBudgetsCount: 10,
  updatedKeywordsCount: 20,
  deletedV2Count: 40,
  customPreservedCount: 2,
  backupPath: "/tmp/backup.sref",
};

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("migrationReducer", () => {
  it("starts on discover with no plan / backup / outcome", () => {
    expect(INITIAL_STATE.step).toBe("discover");
    expect(INITIAL_STATE.plan).toBeNull();
    expect(INITIAL_STATE.backup).toBeNull();
    expect(INITIAL_STATE.outcome).toBeNull();
    expect(INITIAL_STATE.unresolved).toBe(0);
  });

  it("LOAD_PLAN stores plan and counts unresolved rows", () => {
    const plan = makePlan([makeRow(10, 1011), makeRow(11, null)]);
    const next = migrationReducer(INITIAL_STATE, { type: "LOAD_PLAN", plan });
    expect(next.plan).toBe(plan);
    expect(next.unresolved).toBe(1);
    expect(next.errors).toEqual([]);
  });

  it("RESOLVE_ROW sets target, bumps confidence from none to medium, decreases unresolved", () => {
    const plan = makePlan([makeRow(10, null), makeRow(11, null)]);
    const s1 = migrationReducer(INITIAL_STATE, { type: "LOAD_PLAN", plan });
    expect(s1.unresolved).toBe(2);
    const s2 = migrationReducer(s1, {
      type: "RESOLVE_ROW",
      v2CategoryId: 10,
      v1TargetId: 1011,
      v1TargetName: "Paie régulière",
    });
    expect(s2.unresolved).toBe(1);
    const resolved = s2.plan!.rows.find((r) => r.v2CategoryId === 10)!;
    expect(resolved.v1TargetId).toBe(1011);
    expect(resolved.v1TargetName).toBe("Paie régulière");
    expect(resolved.confidence).toBe("medium");
  });

  it("RESOLVE_ROW resolves a preserved custom category and bumps its confidence (#259)", () => {
    const plan = makePlan([makeRow(10, 1011)], [makeRow(9001, null)]);
    const s1 = migrationReducer(INITIAL_STATE, { type: "LOAD_PLAN", plan });
    const s2 = migrationReducer(s1, {
      type: "RESOLVE_ROW",
      v2CategoryId: 9001,
      v1TargetId: 1111,
      v1TargetName: "Épicerie régulière",
    });
    const merged = s2.plan!.preserved.find((r) => r.v2CategoryId === 9001)!;
    expect(merged.v1TargetId).toBe(1111);
    expect(merged.v1TargetName).toBe("Épicerie régulière");
    expect(merged.confidence).toBe("medium");
  });

  it("RESOLVE_ROW on a preserved custom does NOT change unresolved (seed rows only) (#259)", () => {
    // One unresolved seed row + one custom: the guard counts the seed only.
    const plan = makePlan([makeRow(10, null)], [makeRow(9001, null)]);
    const s1 = migrationReducer(INITIAL_STATE, { type: "LOAD_PLAN", plan });
    expect(s1.unresolved).toBe(1);
    const s2 = migrationReducer(s1, {
      type: "RESOLVE_ROW",
      v2CategoryId: 9001,
      v1TargetId: 1111,
      v1TargetName: "Épicerie régulière",
    });
    // Merging the custom must not decrement the seed-row guard.
    expect(s2.unresolved).toBe(1);
  });

  it("GO_NEXT advances simulate -> consent with an unmerged custom still present (#259)", () => {
    // All seed rows resolved, one custom left unmerged: the wizard must proceed.
    const plan = makePlan([makeRow(10, 1011)], [makeRow(9001, null)]);
    let s = migrationReducer(INITIAL_STATE, { type: "LOAD_PLAN", plan });
    s = migrationReducer(s, { type: "GO_NEXT" }); // discover -> simulate
    expect(s.step).toBe("simulate");
    s = migrationReducer(s, { type: "GO_NEXT" }); // simulate -> consent
    expect(s.step).toBe("consent");
  });

  it("GO_NEXT blocks simulate -> consent when unresolved > 0", () => {
    const plan = makePlan([makeRow(10, null)]);
    let s = migrationReducer(INITIAL_STATE, { type: "LOAD_PLAN", plan });
    s = migrationReducer(s, { type: "GO_NEXT" }); // discover -> simulate
    expect(s.step).toBe("simulate");
    const sBlocked = migrationReducer(s, { type: "GO_NEXT" });
    expect(sBlocked.step).toBe("simulate"); // blocked because unresolved=1
  });

  it("GO_NEXT advances simulate -> consent once all rows are resolved", () => {
    const plan = makePlan([makeRow(10, null)]);
    let s = migrationReducer(INITIAL_STATE, { type: "LOAD_PLAN", plan });
    s = migrationReducer(s, { type: "GO_NEXT" }); // simulate
    s = migrationReducer(s, {
      type: "RESOLVE_ROW",
      v2CategoryId: 10,
      v1TargetId: 1011,
      v1TargetName: "Paie régulière",
    });
    s = migrationReducer(s, { type: "GO_NEXT" }); // consent
    expect(s.step).toBe("consent");
  });

  it("GO_BACK goes simulate -> discover and consent -> simulate", () => {
    const plan = makePlan([makeRow(10, 1011)]);
    let s = migrationReducer(INITIAL_STATE, { type: "LOAD_PLAN", plan });
    s = migrationReducer(s, { type: "GO_NEXT" }); // simulate
    s = migrationReducer(s, { type: "GO_NEXT" }); // consent
    expect(s.step).toBe("consent");
    s = migrationReducer(s, { type: "GO_BACK" });
    expect(s.step).toBe("simulate");
    s = migrationReducer(s, { type: "GO_BACK" });
    expect(s.step).toBe("discover");
  });

  it("START_RUN moves the step to running and clears errors", () => {
    const base = { ...INITIAL_STATE, step: "consent" as const, errors: ["old"] };
    const s = migrationReducer(base, { type: "START_RUN" });
    expect(s.step).toBe("running");
    expect(s.errors).toEqual([]);
  });

  it("SET_BACKUP stores the backup and keeps the current step", () => {
    const base = { ...INITIAL_STATE, step: "running" as const };
    const s = migrationReducer(base, { type: "SET_BACKUP", backup: FAKE_BACKUP });
    expect(s.backup).toBe(FAKE_BACKUP);
    expect(s.step).toBe("running");
  });

  it("SET_OUTCOME moves to success on succeeded=true", () => {
    const base = { ...INITIAL_STATE, step: "running" as const };
    const s = migrationReducer(base, {
      type: "SET_OUTCOME",
      outcome: FAKE_OUTCOME,
    });
    expect(s.step).toBe("success");
    expect(s.outcome).toBe(FAKE_OUTCOME);
    expect(s.errors).toEqual([]);
  });

  it("SET_OUTCOME moves to error when outcome.succeeded=false and records the error", () => {
    const failed: MigrationOutcome = { ...FAKE_OUTCOME, succeeded: false, error: "sql" };
    const base = { ...INITIAL_STATE, step: "running" as const };
    const s = migrationReducer(base, { type: "SET_OUTCOME", outcome: failed });
    expect(s.step).toBe("error");
    expect(s.outcome).toBe(failed);
    expect(s.errors).toContain("sql");
  });

  it("FAIL appends to errors and moves to error step", () => {
    const base = { ...INITIAL_STATE, step: "running" as const };
    const s = migrationReducer(base, { type: "FAIL", error: "backup failed" });
    expect(s.step).toBe("error");
    expect(s.errors).toEqual(["backup failed"]);
  });

  it("SELECT_ROW stores the selected row id", () => {
    const s1 = migrationReducer(INITIAL_STATE, { type: "SELECT_ROW", v2CategoryId: 42 });
    expect(s1.selectedRowV2Id).toBe(42);
    const s2 = migrationReducer(s1, { type: "SELECT_ROW", v2CategoryId: null });
    expect(s2.selectedRowV2Id).toBeNull();
  });

  it("RESET returns the initial state", () => {
    const middle = {
      ...INITIAL_STATE,
      step: "success" as const,
      errors: ["x"],
      outcome: FAKE_OUTCOME,
    };
    const s = migrationReducer(middle, { type: "RESET" });
    expect(s).toEqual(INITIAL_STATE);
  });
});
