import { assert, assertEquals, assertStringIncludes } from "@std/assert";
import { join } from "@std/path";
import { TempDir } from "./helpers/temp.ts";
import { initSettingsFile, applyP2pSettings, applyP2pTestTweaks } from "./helpers/settings.ts";
import { startCliInBackground } from "./helpers/backgroundCli.ts";
import { maybeStartLocalRelay, stopLocalRelayIfStarted, maybeStartCoturn, stopCoturnIfStarted } from "./helpers/p2p.ts";
import { CLI_DIR, runCli, sanitiseCatStdout } from "./helpers/cli.ts";

const NOTE_PATH = "p2p-replicator-replacement.md";
const NOTE_CONTENT = "Replicated after replacing the active P2P replicator.";

async function runReplacementProbe(
    vaultPath: string,
    settingsPath: string,
    targetPeer: string,
    timeoutMs: number
): Promise<{ code: number; stdout: string; stderr: string }> {
    const command = new Deno.Command("node", {
        args: [
            join(CLI_DIR, "dist", "p2p-lifecycle-test.cjs"),
            vaultPath,
            "--settings",
            settingsPath,
            "p2p-sync",
            targetPeer,
            String(timeoutMs / 1000),
            NOTE_PATH,
            NOTE_CONTENT,
        ],
        cwd: CLI_DIR,
        stdout: "piped",
        stderr: "piped",
    });
    const result = await command.output();
    return {
        code: result.code,
        stdout: new TextDecoder().decode(result.stdout),
        stderr: new TextDecoder().decode(result.stderr),
    };
}

Deno.test("p2p lifecycle: active-adapter replacement keeps real CLI communication on the stable service", async () => {
    const relay = Deno.env.get("RELAY") ?? "ws://localhost:4000/";
    const peersTimeout = Number(Deno.env.get("PEERS_TIMEOUT") ?? "20");
    const syncTimeout = Number(Deno.env.get("SYNC_TIMEOUT") ?? "60");
    const probeTimeoutMs = Math.max(peersTimeout, syncTimeout) * 1000;
    const nonce = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
    const roomId = Deno.env.get("ROOM_ID") ?? `replacement-room-${nonce}`;
    const passphrase = Deno.env.get("PASSPHRASE") ?? `replacement-pass-${nonce}`;
    const appId = "self-hosted-livesync-cli-replacement-test";
    const hostPeerName = `p2p-replacement-host-${nonce}`;
    const probePeerName = `p2p-replacement-probe-${nonce}`;
    const verifierPeerName = `p2p-replacement-verifier-${nonce}`;
    const useCoturn = Deno.env.get("LIVESYNC_USE_COTURN") !== "0";
    const turnServers = Deno.env.get("TURN_SERVERS") ?? (useCoturn ? "turn:127.0.0.1:3478" : "none");

    await using workDir = await TempDir.create("livesync-cli-p2p-replacement");
    const hostVault = workDir.join("vault-host");
    const probeVault = workDir.join("vault-probe");
    const verifierVault = workDir.join("vault-verifier");
    const hostSettings = workDir.join("settings-host.json");
    const probeSettings = workDir.join("settings-probe.json");
    const verifierSettings = workDir.join("settings-verifier.json");
    await Promise.all([
        Deno.mkdir(hostVault, { recursive: true }),
        Deno.mkdir(probeVault, { recursive: true }),
        Deno.mkdir(verifierVault, { recursive: true }),
    ]);

    const relayStarted = await maybeStartLocalRelay(relay);
    const coturnStarted = await maybeStartCoturn(turnServers);
    try {
        for (const settingsPath of [hostSettings, probeSettings, verifierSettings]) {
            await initSettingsFile(settingsPath);
            await applyP2pSettings(settingsPath, roomId, passphrase, appId, relay, "~.*", turnServers);
        }
        await applyP2pTestTweaks(hostSettings, hostPeerName, passphrase);
        await applyP2pTestTweaks(probeSettings, probePeerName, passphrase);
        await applyP2pTestTweaks(verifierSettings, verifierPeerName, passphrase);

        const host = startCliInBackground(hostVault, "--settings", hostSettings, "p2p-host");
        try {
            await host.waitUntilContains("P2P host is running", 20000);
            const probe = await runReplacementProbe(probeVault, probeSettings, hostPeerName, probeTimeoutMs);
            assert(probe.code === 0, `P2P replacement probe failed\nstdout: ${probe.stdout}\nstderr: ${probe.stderr}`);
            assertStringIncludes(probe.stdout, "[Probe] The active P2P adapter was replaced");

            const syncResult = await runCli(
                verifierVault,
                "--settings",
                verifierSettings,
                "p2p-sync",
                hostPeerName,
                String(syncTimeout)
            );
            assert(
                syncResult.code === 0,
                `Verifier P2P sync failed\nstdout: ${syncResult.stdout}\nstderr: ${syncResult.stderr}`
            );

            const catResult = await runCli(verifierVault, "--settings", verifierSettings, "cat", NOTE_PATH);
            assert(
                catResult.code === 0,
                `Verifier could not read ${NOTE_PATH}\nstdout: ${catResult.stdout}\nstderr: ${catResult.stderr}`
            );
            assertEquals(sanitiseCatStdout(catResult.stdout).trim(), NOTE_CONTENT);
        } finally {
            await host.stop();
        }
    } finally {
        await stopLocalRelayIfStarted(relayStarted);
        await stopCoturnIfStarted(coturnStarted);
    }
});
