"use strict";
/**
 * test_atomic_writer — Story 6.1 regression suite.
 *
 * Asserts the AtomicWriter contract:
 *   1. Happy path round-trip
 *   2. Crash safety (rename failure leaves no corrupted final file)
 *   3. OPENLIFE_ATOMIC_WRITES=off fallback to plain writeFileSync
 *   4. JSONL append atomicity
 *
 * Run via: `npm run test:atomic-writer` (compiles + executes dist/).
 *
 * Pattern: throws or process.exit(1) on failure; prints
 * TEST_ATOMIC_WRITER_OK on full pass.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const AtomicWriter_1 = require("./orchestrator/util/AtomicWriter");
function assert(cond, msg) {
    if (!cond)
        throw new Error(`assertion failed: ${msg}`);
}
function mkTmp() {
    return fs.mkdtempSync(path.join(os.tmpdir(), 'atomic-test-'));
}
function rmTmp(dir) {
    try {
        fs.rmSync(dir, { recursive: true, force: true });
    }
    catch { /* ignore */ }
}
function listTempOrphans(dir) {
    if (!fs.existsSync(dir))
        return [];
    return fs.readdirSync(dir).filter((f) => /\.tmp\.\d+\.\d+\.[a-z0-9]+$/i.test(f));
}
// ─────────────────────────────────────────────────────────────
// Scenario 1: Happy path round-trip
// ─────────────────────────────────────────────────────────────
function scenarioHappyPath() {
    const tmpRoot = mkTmp();
    try {
        const target = path.join(tmpRoot, 'state.json');
        const payload = { hello: 'world', count: 42, nested: { ok: true } };
        (0, AtomicWriter_1.writeJsonAtomic)(target, payload);
        assert(fs.existsSync(target), 'final file must exist after writeJsonAtomic');
        const read = JSON.parse(fs.readFileSync(target, 'utf-8'));
        assert(JSON.stringify(read) === JSON.stringify(payload), 'round-trip mismatch');
        assert(listTempOrphans(tmpRoot).length === 0, 'no temp file should remain after success');
        console.log('✅ scenario 1: happy path round-trip');
    }
    finally {
        rmTmp(tmpRoot);
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 2: Crash safety — rename fails, no corrupted file
// ─────────────────────────────────────────────────────────────
function scenarioCrashSafety() {
    const tmpRoot = mkTmp();
    try {
        // Pre-existing file in valid parent that we expect to remain unchanged
        const target = path.join(tmpRoot, 'state.json');
        fs.writeFileSync(target, JSON.stringify({ before: 'unchanged' }), 'utf-8');
        // Simulate "crash mid-write" by aiming at a target whose parent
        // exists at writeJsonAtomic call time but is deleted before the rename
        // could succeed. We can't reliably race that, so instead we trigger a
        // hard error path: pre-create the final path as a *directory*, which
        // makes renameSync throw with EISDIR / EEXIST.
        //
        // This exercises the same code path: write tmp → attempt rename → throw
        // → ensure pre-existing data on `target` is untouched + temp orphan
        // cleaned up.
        const collidingDir = path.join(tmpRoot, 'collision');
        fs.mkdirSync(collidingDir, { recursive: true });
        // Place a file inside so the dir is non-empty — renameSync onto a
        // non-empty dir is guaranteed to fail across POSIX + Windows.
        fs.writeFileSync(path.join(collidingDir, 'sentinel'), 'x', 'utf-8');
        let threw = false;
        try {
            (0, AtomicWriter_1.writeJsonAtomic)(collidingDir, { after: 'never-arrives' });
        }
        catch (err) {
            threw = err instanceof Error;
        }
        assert(threw, 'writeJsonAtomic must throw when the rename target is a non-empty directory');
        // The colliding directory should still contain its sentinel — no corruption
        assert(fs.existsSync(path.join(collidingDir, 'sentinel')), 'colliding dir contents must not be wiped');
        // The original target should be untouched (we never wrote to it on this attempt)
        const stillThere = JSON.parse(fs.readFileSync(target, 'utf-8'));
        assert(stillThere.before === 'unchanged', 'pre-existing target must not be overwritten by failed call');
        // purgeStaleTempFiles should be invocable + non-error (cleanup helper)
        const removed = (0, AtomicWriter_1.purgeStaleTempFiles)(tmpRoot, 0);
        assert(typeof removed === 'number' && removed >= 0, 'purgeStaleTempFiles must return a non-negative count');
        console.log('✅ scenario 2: crash safety (rename failure does not corrupt existing data)');
    }
    finally {
        rmTmp(tmpRoot);
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 3: OPENLIFE_ATOMIC_WRITES=off fallback
// ─────────────────────────────────────────────────────────────
function scenarioFallback() {
    const tmpRoot = mkTmp();
    const prev = process.env.OPENLIFE_ATOMIC_WRITES;
    try {
        process.env.OPENLIFE_ATOMIC_WRITES = 'off';
        assert((0, AtomicWriter_1._isAtomicEnabled)() === false, 'flag=off must disable atomic mode');
        const target = path.join(tmpRoot, 'state.json');
        (0, AtomicWriter_1.writeJsonAtomic)(target, { mode: 'fallback' });
        assert(fs.existsSync(target), 'fallback mode must still write file');
        const read = JSON.parse(fs.readFileSync(target, 'utf-8'));
        assert(read.mode === 'fallback', 'fallback content must be intact');
        assert(listTempOrphans(tmpRoot).length === 0, 'fallback mode must not create temp files');
        // Restore — re-check enabled
        delete process.env.OPENLIFE_ATOMIC_WRITES;
        assert((0, AtomicWriter_1._isAtomicEnabled)() === true, 'default (no env) must be atomic enabled');
        console.log('✅ scenario 3: OPENLIFE_ATOMIC_WRITES=off fallback');
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_ATOMIC_WRITES;
        else
            process.env.OPENLIFE_ATOMIC_WRITES = prev;
        rmTmp(tmpRoot);
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 4: JSONL append atomicity
// ─────────────────────────────────────────────────────────────
function scenarioJsonlAppend() {
    const tmpRoot = mkTmp();
    try {
        const target = path.join(tmpRoot, 'events.jsonl');
        (0, AtomicWriter_1.appendJsonlAtomic)(target, { type: 'a', n: 1 });
        (0, AtomicWriter_1.appendJsonlAtomic)(target, { type: 'b', n: 2 });
        (0, AtomicWriter_1.appendJsonlAtomic)(target, { type: 'c', n: 3, nested: { ok: true } });
        const lines = fs.readFileSync(target, 'utf-8').split('\n').filter((l) => l.length > 0);
        assert(lines.length === 3, `expected 3 JSONL lines, got ${lines.length}`);
        const parsed = lines.map((l) => JSON.parse(l));
        assert(parsed[0].type === 'a' && parsed[0].n === 1, 'line 1 mismatch');
        assert(parsed[1].type === 'b' && parsed[1].n === 2, 'line 2 mismatch');
        assert(parsed[2].type === 'c' && parsed[2].n === 3, 'line 3 mismatch');
        assert(listTempOrphans(tmpRoot).length === 0, 'no temp orphans after JSONL appends');
        // Also write to non-existing parent dir to verify auto-mkdir
        const nested = path.join(tmpRoot, 'deeper', 'log.jsonl');
        (0, AtomicWriter_1.appendJsonlAtomic)(nested, { type: 'nested', n: 99 });
        assert(fs.existsSync(nested), 'auto-mkdir for nested JSONL append');
        console.log('✅ scenario 4: JSONL append atomicity');
    }
    finally {
        rmTmp(tmpRoot);
    }
}
// ─────────────────────────────────────────────────────────────
// Driver
// ─────────────────────────────────────────────────────────────
function main() {
    console.log('🧪 test_atomic_writer — Story 6.1 regression suite');
    scenarioHappyPath();
    scenarioCrashSafety();
    scenarioFallback();
    scenarioJsonlAppend();
    console.log('');
    console.log('TEST_ATOMIC_WRITER_OK');
}
try {
    main();
}
catch (err) {
    console.error('❌ test_atomic_writer FAILED:', err instanceof Error ? err.message : err);
    process.exit(1);
}
