// acceptance gate for the port writer + drift-recovery flow.
//
// Three invariants this test protects:
//   (a) PORT stays public across install → upgrade on a fresh fs fixture.
//       Without this, each upgrade re-inherits the internal port as if it
//       were public — the exact regression this task fixes.
//   (b) Drift-recovery logs fire EXACTLY once when a pre-task maxy.service
//       with a drifted PORT meets a post-task installer. A second upgrade
//       on the same device must be silent.
//   (c) MAXY_UI_INTERNAL_PORT is PORT + 1 in every written unit file, so
//       maxy-ui binds the internal port while the tunnel still hits edge.
//
// Runs via Node's built-in test runner; no vitest dependency. Compiles to
// dist/__tests__/port-canonicalisation.test.js alongside the rest of the
// package so `node --test dist/__tests__/*.test.js` picks it up after build.
import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { resolveInstallPort, resolveInstallPortFromFs, buildMaxyUnitFile, } from "../port-resolution.js";
function makeUnitFile(port, internal) {
    return buildMaxyUnitFile({
        productName: "Maxy",
        brandHostname: "maxy",
        neo4jDedicated: false,
        installDir: "/home/me/maxy",
        persistDir: "/home/me/.maxy",
        port,
        maxyUiInternalPort: internal,
        vncDisplay: 99,
        rfbPort: 5900,
        websockifyPort: 6080,
        cdpPort: 9222,
        claudeSessionManagerPort: 19400,
        chromiumBin: "/usr/bin/chromium",
        accountId: "00000000-0000-0000-0000-000000000001",
    });
}
function makeEdgeFile(edgePort) {
    return `[Unit]\nDescription=Edge\n\n[Service]\nEnvironment=EDGE_PORT=${edgePort}\nEnvironment=MAXY_UI_PORT=${edgePort + 1}\n`;
}
// ---------------------------------------------------------------------------
// (a) Fresh install writes PORT=public + MAXY_UI_INTERNAL_PORT=public+1 —
// and a subsequent upgrade reads the SAME public value back. No drift.
// ---------------------------------------------------------------------------
test("fresh install then upgrade keeps PORT public (no drift)", () => {
    const PUBLIC = 19200;
    const INTERNAL = PUBLIC + 1;
    // Fresh install: no existing service file, no .env → default.
    const first = resolveInstallPort({
        envContent: null,
        maxyServiceContent: null,
        edgeServiceContent: null,
    });
    assert.equal(first.port, PUBLIC);
    assert.equal(first.source, "default");
    assert.equal(first.driftLogs.length, 0);
    // Installer writes the unit files with PORT=public, MAXY_UI_INTERNAL_PORT=+1.
    const maxyUnit = makeUnitFile(first.port, first.port + 1);
    const edgeUnit = makeEdgeFile(first.port);
    // Upgrade reads them back. Same public value, no drift.
    const second = resolveInstallPort({
        envContent: null,
        maxyServiceContent: maxyUnit,
        edgeServiceContent: edgeUnit,
    });
    assert.equal(second.port, PUBLIC, "upgrade must not drift the public port");
    assert.equal(second.source, "running service");
    assert.equal(second.driftLogs.length, 0, "no drift expected on clean device");
    // And one more upgrade for good measure — still silent.
    const third = resolveInstallPort({
        envContent: null,
        maxyServiceContent: maxyUnit,
        edgeServiceContent: edgeUnit,
    });
    assert.equal(third.port, PUBLIC);
    assert.equal(third.driftLogs.length, 0);
    // Unit file literal invariants (c): PORT is the public, INTERNAL is +1.
    assert.match(maxyUnit, /^Environment=PORT=19200$/m);
    assert.match(maxyUnit, /^Environment=MAXY_UI_INTERNAL_PORT=19201$/m);
});
// ---------------------------------------------------------------------------
// (b) Drifted device: pre-task maxy.service has the INTERNAL port (19201) in
// the PORT slot, edge has the canonical public (19200). Recovery logs once,
// pins at edge, and the next pass on the rewritten files is silent.
// ---------------------------------------------------------------------------
test("drifted device recovers in one pass, subsequent upgrade is silent", () => {
    const TRUE_PUBLIC = 19200;
    // Pre-task unit: Environment=PORT=<internal>. Edge correctly carries public.
    const driftedMaxy = makeUnitFile(TRUE_PUBLIC + 1, TRUE_PUBLIC + 2); // drifted
    const edgeAtTruth = makeEdgeFile(TRUE_PUBLIC);
    const recovery = resolveInstallPort({
        envContent: null,
        maxyServiceContent: driftedMaxy,
        edgeServiceContent: edgeAtTruth,
    });
    assert.equal(recovery.port, TRUE_PUBLIC, "edge wins over drifted maxy");
    assert.equal(recovery.source, "edge-recovery");
    assert.equal(recovery.driftLogs.length, 1, "exactly one drift line");
    assert.match(recovery.driftLogs[0], /\[port-recovery\] detected drift maxy=19201 edge=19200 — pinning at 19200/);
    // After recovery, the installer writes a corrected maxy.service.
    const rewritten = makeUnitFile(recovery.port, recovery.port + 1);
    // Next upgrade on the same device: both files agree, no log line.
    const silent = resolveInstallPort({
        envContent: null,
        maxyServiceContent: rewritten,
        edgeServiceContent: edgeAtTruth,
    });
    assert.equal(silent.port, TRUE_PUBLIC);
    assert.equal(silent.source, "running service");
    assert.equal(silent.driftLogs.length, 0, "post-recovery upgrade must be silent");
});
// ---------------------------------------------------------------------------
// --port flag short-circuits every source, including drift recovery (the
// operator is asserting a port explicitly).
// ---------------------------------------------------------------------------
test("--port flag short-circuits drift recovery", () => {
    const driftedMaxy = makeUnitFile(20001, 20002);
    const edge = makeEdgeFile(19200);
    const result = resolveInstallPort({
        portFlag: 31000,
        envContent: null,
        maxyServiceContent: driftedMaxy,
        edgeServiceContent: edge,
    });
    assert.equal(result.port, 31000);
    assert.equal(result.source, "--port flag");
    assert.equal(result.driftLogs.length, 0);
});
// ---------------------------------------------------------------------------
// Legacy pre-647 device (no edge unit): reader uses maxy.service's PORT value,
// no drift log emitted.
// ---------------------------------------------------------------------------
test("pre-647 device without edge unit reads maxy PORT, no drift log", () => {
    const legacyMaxy = `[Service]\nEnvironment=PORT=19200\n`;
    const result = resolveInstallPort({
        envContent: null,
        maxyServiceContent: legacyMaxy,
        edgeServiceContent: null,
    });
    assert.equal(result.port, 19200);
    assert.equal(result.source, "running service");
    assert.equal(result.driftLogs.length, 0);
});
// ---------------------------------------------------------------------------
// .env override takes priority over the service file; drift recovery still
// runs against whatever was computed.
// ---------------------------------------------------------------------------
test(".env override wins over service file; drift recovery still runs", () => {
    const env = "PORT=19500\nEMBED_MODEL=nomic\n";
    const maxy = makeUnitFile(19200, 19201);
    const edge = makeEdgeFile(19500);
    // .env says 19500, maxy.service says 19200, edge says 19500. Reader
    // picks .env (19500), edge confirms (19500) — no drift.
    const resolved = resolveInstallPort({
        envContent: env,
        maxyServiceContent: maxy,
        edgeServiceContent: edge,
    });
    assert.equal(resolved.port, 19500);
    assert.equal(resolved.source, ".env override");
    assert.equal(resolved.driftLogs.length, 0);
    // Now the .env says 19202 (drifted), edge says 19500 — drift recovery wins.
    const driftedEnv = "PORT=19202\nEMBED_MODEL=nomic\n";
    const recovered = resolveInstallPort({
        envContent: driftedEnv,
        maxyServiceContent: maxy,
        edgeServiceContent: edge,
    });
    assert.equal(recovered.port, 19500);
    assert.equal(recovered.source, "edge-recovery");
    assert.equal(recovered.driftLogs.length, 1);
});
// ---------------------------------------------------------------------------
// fs wrapper end-to-end: real tmpdir fixture, verifies the file-reading path.
// ---------------------------------------------------------------------------
test("fs wrapper reads real fixture files end-to-end", () => {
    const tmp = mkdtempSync(join(tmpdir(), "maxy-port-test-"));
    try {
        const persistDir = join(tmp, ".maxy");
        const serviceDir = join(tmp, ".config", "systemd", "user");
        mkdirSync(persistDir, { recursive: true });
        mkdirSync(serviceDir, { recursive: true });
        writeFileSync(join(serviceDir, "maxy.service"), makeUnitFile(19300, 19301));
        writeFileSync(join(serviceDir, "maxy-edge.service"), makeEdgeFile(19300));
        const result = resolveInstallPortFromFs({
            persistDir,
            serviceDir,
            brandServiceFileName: "maxy.service",
            brandEdgeServiceFileName: "maxy-edge.service",
        });
        assert.equal(result.port, 19300);
        assert.equal(result.source, "running service");
        assert.equal(result.driftLogs.length, 0);
    }
    finally {
        rmSync(tmp, { recursive: true, force: true });
    }
});
