import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import Database from "better-sqlite3";
import { runMigrations, getSchemaVersion, getAppliedMigrations } from "../migrator.js";
vi.mock("../../utils/logger.js", () => ({
    logger: {
        debug: vi.fn(),
        info: vi.fn(),
        warn: vi.fn(),
        error: vi.fn(),
    },
}));
describe("migrator", () => {
    let tempDir;
    let db;
    const testMigrations = [
        {
            version: 1,
            name: "create-users",
            up(db) {
                db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)");
            },
        },
        {
            version: 2,
            name: "add-email-column",
            up(db) {
                db.exec("ALTER TABLE users ADD COLUMN email TEXT");
            },
        },
    ];
    beforeEach(async () => {
        tempDir = await mkdtemp(join(tmpdir(), "pocketcoder-migrator-test-"));
        db = new Database(join(tempDir, "test.db"));
        db.pragma("journal_mode = WAL");
        db.pragma("foreign_keys = ON");
    });
    afterEach(async () => {
        db.close();
        await rm(tempDir, { recursive: true, force: true });
    });
    it("should run pending migrations", () => {
        runMigrations(db, testMigrations);
        // Verify both migrations ran
        const tables = db
            .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != '_migrations' ORDER BY name")
            .all();
        expect(tables.map((t) => t.name)).toContain("users");
        // Verify email column was added
        const columns = db.prepare("PRAGMA table_info(users)").all();
        expect(columns.map((c) => c.name)).toContain("email");
    });
    it("should skip applied migrations", () => {
        // Run migrations first time
        runMigrations(db, testMigrations);
        // Run again - should not error (idempotent)
        runMigrations(db, testMigrations);
        // Verify schema version is still 2
        expect(getSchemaVersion(db)).toBe(2);
        // Verify only 2 entries in _migrations table
        const applied = getAppliedMigrations(db);
        expect(applied).toHaveLength(2);
    });
    it("should track versions", () => {
        runMigrations(db, testMigrations);
        const version = getSchemaVersion(db);
        expect(version).toBe(2);
        // Also verify user_version pragma is set
        const userVersion = db.pragma("user_version", { simple: true });
        expect(userVersion).toBe(2);
    });
    it("should create _migrations table", () => {
        runMigrations(db, testMigrations);
        const tables = db
            .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'")
            .all();
        expect(tables).toHaveLength(1);
    });
    it("should record applied migrations with metadata", () => {
        runMigrations(db, testMigrations);
        const applied = getAppliedMigrations(db);
        expect(applied).toHaveLength(2);
        expect(applied[0]).toMatchObject({ version: 1, name: "create-users" });
        expect(applied[1]).toMatchObject({
            version: 2,
            name: "add-email-column",
        });
        // applied_at should be set
        expect(applied[0].applied_at).toBeTruthy();
        expect(applied[1].applied_at).toBeTruthy();
    });
    it("should only run new migrations when some already applied", () => {
        // Run first migration only
        runMigrations(db, [testMigrations[0]]);
        expect(getSchemaVersion(db)).toBe(1);
        // Now run both - should only apply v2
        runMigrations(db, testMigrations);
        expect(getSchemaVersion(db)).toBe(2);
        const applied = getAppliedMigrations(db);
        expect(applied).toHaveLength(2);
    });
    it("should handle migration failures gracefully", () => {
        const failingMigrations = [
            testMigrations[0],
            {
                version: 2,
                name: "failing-migration",
                up() {
                    throw new Error("Intentional migration failure");
                },
            },
        ];
        expect(() => runMigrations(db, failingMigrations)).toThrow(/Migration v2.*failed.*Intentional migration failure/);
        // v1 should still be applied (was committed before v2 attempted)
        expect(getSchemaVersion(db)).toBe(1);
        const applied = getAppliedMigrations(db);
        expect(applied).toHaveLength(1);
        expect(applied[0].version).toBe(1);
    });
    it("should backfill from user_version pragma", () => {
        // Simulate an old database with user_version set but no _migrations table
        db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)");
        db.pragma("user_version = 1");
        // Run all migrations - should backfill v1 and apply v2
        runMigrations(db, testMigrations);
        const applied = getAppliedMigrations(db);
        expect(applied).toHaveLength(2);
        expect(applied[0].version).toBe(1);
        expect(applied[1].version).toBe(2);
        expect(getSchemaVersion(db)).toBe(2);
    });
    it("should handle empty migration list", () => {
        runMigrations(db, []);
        expect(getSchemaVersion(db)).toBe(0);
        expect(getAppliedMigrations(db)).toHaveLength(0);
    });
    it("should return 0 for fresh database with no migrations table", () => {
        expect(getSchemaVersion(db)).toBe(0);
    });
    it("should return empty array for getAppliedMigrations on fresh database", () => {
        expect(getAppliedMigrations(db)).toEqual([]);
    });
});
//# sourceMappingURL=migrator.test.js.map