import { describe, it, beforeEach } from "node:test";
import assert from "node:assert/strict";
import { getOnboardingState, completeOnboardingStep } from "./onboarding.js";
import { mkdtempSync, writeFileSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
// Mock Neo4j session factory
function createMockSession(records = []) {
    const mockRecords = records.map((r) => ({
        get(key) { return r[key]; },
    }));
    return {
        async run(_query, _params) {
            return { records: mockRecords };
        },
        async close() { },
    };
}
// Stateful mock that tracks MERGE/SET calls and returns dynamic records
function createStatefulMockSession(opts = {}) {
    const queries = [];
    return {
        queries,
        async run(query, params) {
            queries.push({ query, params });
            if (query.includes("MERGE") && opts.mergeRecords) {
                return { records: opts.mergeRecords.map((r) => ({ get(key) { return r[key]; } })) };
            }
            if (query.includes("MATCH") && query.includes("SET") && opts.mergeRecords) {
                return { records: opts.mergeRecords.map((r) => ({ get(key) { return r[key]; } })) };
            }
            if (opts.matchRecords) {
                return { records: opts.matchRecords.map((r) => ({ get(key) { return r[key]; } })) };
            }
            return { records: [] };
        },
        async close() { },
    };
}
function makeTmpAccountDir() {
    const dir = mkdtempSync(join(tmpdir(), "maxy-test-"));
    return dir;
}
// ============================================================
// getOnboardingState
// ============================================================
describe("getOnboardingState", () => {
    let accountDir;
    beforeEach(() => {
        accountDir = makeTmpAccountDir();
    });
    it("returns existing state when OnboardingState node exists", async () => {
        const now = new Date().toISOString();
        const session = createMockSession([{
                currentStep: 3,
                step1CompletedAt: now,
                step2CompletedAt: now,
                step3CompletedAt: now,
                step4CompletedAt: null,
                step5CompletedAt: null,
                step6CompletedAt: null,
                step7CompletedAt: null,
                createdAt: now,
                updatedAt: now,
            }]);
        const state = await getOnboardingState(() => session, "test-account", accountDir);
        assert.equal(state.currentStep, 3);
        assert.equal(state.step1CompletedAt, now);
        assert.equal(state.step4CompletedAt, null);
    });
    it("migrates from account.json when no graph node exists", async () => {
        // Write account.json with onboardingStep
        writeFileSync(join(accountDir, "account.json"), JSON.stringify({
            accountId: "test-account",
            onboardingStep: 4,
            outputStyle: "default",
        }));
        const now = new Date().toISOString();
        const session = createStatefulMockSession({
            matchRecords: undefined, // no existing node
            mergeRecords: [{
                    currentStep: 4,
                    step1CompletedAt: now,
                    step2CompletedAt: now,
                    step3CompletedAt: now,
                    step4CompletedAt: now,
                    step5CompletedAt: null,
                    step6CompletedAt: null,
                    step7CompletedAt: null,
                    createdAt: now,
                    updatedAt: now,
                }],
        });
        const state = await getOnboardingState(() => session, "test-account", accountDir);
        assert.equal(state.currentStep, 4);
        // Verify account.json no longer has onboardingStep
        const updated = JSON.parse(readFileSync(join(accountDir, "account.json"), "utf-8"));
        assert.equal(updated.onboardingStep, undefined);
        assert.equal(updated.outputStyle, "default"); // other fields preserved
    });
    it("creates fresh state when no graph node and no account.json exists", async () => {
        const now = new Date().toISOString();
        const session = createStatefulMockSession({
            mergeRecords: [{
                    currentStep: 0,
                    step1CompletedAt: null,
                    step2CompletedAt: null,
                    step3CompletedAt: null,
                    step4CompletedAt: null,
                    step5CompletedAt: null,
                    step6CompletedAt: null,
                    step7CompletedAt: null,
                    createdAt: now,
                    updatedAt: now,
                }],
        });
        const state = await getOnboardingState(() => session, "test-account", accountDir);
        assert.equal(state.currentStep, 0);
    });
    it("creates fresh state when account.json has no onboardingStep field", async () => {
        writeFileSync(join(accountDir, "account.json"), JSON.stringify({
            accountId: "test-account",
            outputStyle: "default",
        }));
        const now = new Date().toISOString();
        const session = createStatefulMockSession({
            mergeRecords: [{
                    currentStep: 0,
                    step1CompletedAt: null,
                    step2CompletedAt: null,
                    step3CompletedAt: null,
                    step4CompletedAt: null,
                    step5CompletedAt: null,
                    step6CompletedAt: null,
                    step7CompletedAt: null,
                    createdAt: now,
                    updatedAt: now,
                }],
        });
        const state = await getOnboardingState(() => session, "test-account", accountDir);
        assert.equal(state.currentStep, 0);
    });
    it("handles malformed account.json during migration", async () => {
        writeFileSync(join(accountDir, "account.json"), "not valid json {{{");
        const now = new Date().toISOString();
        const session = createStatefulMockSession({
            mergeRecords: [{
                    currentStep: 0,
                    step1CompletedAt: null,
                    step2CompletedAt: null,
                    step3CompletedAt: null,
                    step4CompletedAt: null,
                    step5CompletedAt: null,
                    step6CompletedAt: null,
                    step7CompletedAt: null,
                    createdAt: now,
                    updatedAt: now,
                }],
        });
        const state = await getOnboardingState(() => session, "test-account", accountDir);
        assert.equal(state.currentStep, 0); // defaults to fresh
    });
});
// ============================================================
// completeOnboardingStep
// ============================================================
describe("completeOnboardingStep", () => {
    it("sets timestamp and bumps currentStep for a valid step", async () => {
        const now = new Date().toISOString();
        const session = createStatefulMockSession({
            matchRecords: [{ exists: true }],
            mergeRecords: [{
                    currentStep: 3,
                    step1CompletedAt: now,
                    step2CompletedAt: now,
                    step3CompletedAt: now,
                    step4CompletedAt: null,
                    step5CompletedAt: null,
                    step6CompletedAt: null,
                    step7CompletedAt: null,
                    createdAt: now,
                    updatedAt: now,
                }],
        });
        const state = await completeOnboardingStep(() => session, "test-account", 3);
        assert.equal(state.currentStep, 3);
        assert.ok(state.step3CompletedAt !== null);
    });
    it("is idempotent — completing an already-completed step is a no-op", async () => {
        const existingTimestamp = "2026-03-20T10:00:00.000Z";
        const session = createStatefulMockSession({
            matchRecords: [{ exists: true }],
            mergeRecords: [{
                    currentStep: 5,
                    step1CompletedAt: existingTimestamp,
                    step2CompletedAt: existingTimestamp,
                    step3CompletedAt: existingTimestamp,
                    step4CompletedAt: existingTimestamp,
                    step5CompletedAt: existingTimestamp,
                    step6CompletedAt: null,
                    step7CompletedAt: null,
                    createdAt: existingTimestamp,
                    updatedAt: existingTimestamp,
                }],
        });
        const state = await completeOnboardingStep(() => session, "test-account", 3);
        // Should return current state without error
        assert.equal(state.currentStep, 5);
        assert.equal(state.step3CompletedAt, existingTimestamp);
    });
    it("rejects invalid step numbers", async () => {
        const session = createMockSession();
        await assert.rejects(() => completeOnboardingStep(() => session, "test-account", 0), { message: /step must be between 1 and 7/i });
        await assert.rejects(() => completeOnboardingStep(() => session, "test-account", 8), { message: /step must be between 1 and 7/i });
        await assert.rejects(() => completeOnboardingStep(() => session, "test-account", -1), { message: /step must be between 1 and 7/i });
    });
    it("errors when no OnboardingState node exists", async () => {
        const session = createStatefulMockSession({
            matchRecords: [], // no node
        });
        await assert.rejects(() => completeOnboardingStep(() => session, "test-account", 1), { message: /no onboarding state found/i });
    });
});
//# sourceMappingURL=onboarding.test.js.map