import { test } from "node:test";
import assert from "node:assert/strict";
import {
  buildContainerEnv,
  KEEPALIVE_INTERVAL_MS,
  NUCLEICD_CONTROL_PORT,
  RunnerContainer,
} from "../src/container.ts";
import type { ContainerController } from "../src/container.ts";
import type { Env } from "../src/env.ts";

/** A structural fake of the DO state RunnerContainer reads: the containers runtime handle plus
 * the alarm slice of storage. Lets node:test drive the keepalive chain without the DO runtime. */
function fakeState(container: Partial<ContainerController> | undefined) {
  const calls = { setAlarm: [] as number[], deleteAlarm: 0 };
  const state = {
    container,
    storage: {
      setAlarm: async (time: number) => {
        calls.setAlarm.push(time);
      },
      deleteAlarm: async () => {
        calls.deleteAlarm += 1;
      },
    },
  };
  return { state: state as unknown as DurableObjectState, calls };
}

test("buildContainerEnv carries the full instance contract", () => {
  const env = buildContainerEnv({
    poolId: "p1",
    instanceId: "runner-2",
    epoch: "7",
    instanceToken: "tok",
    controlPlaneURL: "https://runner.example",
    relayURL: "https://relay.example",
    intelligenceMode: "agent",
  });
  assert.deepEqual(env, {
    NUCLEIC_RUNNER_POOL_ID: "p1",
    NUCLEIC_RUNNER_INSTANCE_ID: "runner-2",
    NUCLEIC_RUNNER_EPOCH: "7",
    NUCLEIC_RUNNER_INSTANCE_TOKEN: "tok",
    NUCLEIC_RUNNER_CONTROL_URL: "https://runner.example",
    NUCLEIC_RUNNER_NAME: "Cloud Runner",
    NUCLEIC_RELAY_URL: "https://relay.example",
    NUCLEIC_RUNNER_INTELLIGENCE_MODE: "agent",
  });
});

test("relay URL and intelligence mode are optional (defaults live in nucleicd)", () => {
  const env = buildContainerEnv({
    poolId: "p1",
    instanceId: "runner-1",
    epoch: "1",
    instanceToken: "tok",
    controlPlaneURL: "https://runner.example",
  });
  assert.equal("NUCLEIC_RELAY_URL" in env, false);
  assert.equal("NUCLEIC_RUNNER_INTELLIGENCE_MODE" in env, false);
});

test("nucleicd control port is pinned (pairing proxy contract)", () => {
  assert.equal(NUCLEICD_CONTROL_PORT, 9200);
});

test("keepalive cadence sits inside the DO eviction floor", () => {
  // DOs are evicted after 70–140 s without events, and eviction kills the attached container.
  // The alarm chain is the only thing generating events on an otherwise-idle container DO, so
  // its interval must stay comfortably under the 70 s floor (2026-07 runner-death incidents).
  assert.ok(KEEPALIVE_INTERVAL_MS <= 60_000);
  assert.ok(KEEPALIVE_INTERVAL_MS >= 5_000); // and not so hot it burns alarm invocations
});

test("start arms the keepalive alarm; the alarm re-arms while running", async () => {
  const running = { running: true, start: () => {} };
  const { state, calls } = fakeState(running);
  const dobj = new RunnerContainer(state, {} as Env);
  const res = await dobj.fetch(new Request("https://container/start", { method: "POST" }));
  assert.equal(res.status, 200);
  assert.equal(calls.setAlarm.length, 1);
  await dobj.alarm();
  assert.equal(calls.setAlarm.length, 2);
});

test("stop clears the alarm; a post-exit alarm does not re-arm", async () => {
  const stopped = { running: false, destroy: async () => {} };
  const { state, calls } = fakeState(stopped);
  const dobj = new RunnerContainer(state, {} as Env);
  const res = await dobj.fetch(new Request("https://container/stop", { method: "POST" }));
  assert.equal(res.status, 200);
  assert.equal(calls.deleteAlarm, 1);
  // The container exited on its own (running=false): the chain lapses instead of re-arming.
  await dobj.alarm();
  assert.equal(calls.setAlarm.length, 0);
});
