/**
 * ClaudeCodeAdapter — OpenRouter env wiring and spawn contract (§14).
 */

import assert from "node:assert/strict";
import { EventEmitter } from "node:events";
import { Readable } from "node:stream";
import { test } from "node:test";
import type { ChildProcessWithoutNullStreams, SpawnOptions } from "node:child_process";

import { ClaudeCodeAdapter } from "../ClaudeCodeAdapter.ts";
import type { AgentCreateSpec } from "../AgentSdkAdapter.ts";

function mockChildProcess(result: {
  stdout?: string;
  stderr?: string;
  exitCode?: number;
}): ChildProcessWithoutNullStreams {
  const child = new EventEmitter() as ChildProcessWithoutNullStreams;
  const stdout = new Readable({ read() {} });
  const stderr = new Readable({ read() {} });
  child.stdout = stdout;
  child.stderr = stderr;
  let killed = false;
  child.kill = () => {
    killed = true;
    return true;
  };
  Object.defineProperty(child, "killed", {
    get: () => killed,
    configurable: true,
  });

  setImmediate(() => {
    if (result.stdout) stdout.emit("data", result.stdout);
    if (result.stderr) stderr.emit("data", result.stderr);
    child.emit("close", result.exitCode ?? 0, null);
  });

  return child;
}

function baseCreateSpec(): AgentCreateSpec {
  return {
    agentId: "DEV-01",
    role: "developer",
    layer: "worker",
    runtime: "local",
    workspace: "D:\\proj",
  };
}

function baseAdapterOpts(
  spawnFn: (
    command: string,
    args: readonly string[],
    options: SpawnOptions,
  ) => ChildProcessWithoutNullStreams,
) {
  return {
    authToken: "sk-or-test",
    defaultCwd: "D:\\proj",
    sonnetModel: "anthropic/claude-sonnet-4.5",
    haikuModel: "~anthropic/claude-haiku-latest",
    subagentModel: "~anthropic/claude-haiku-latest",
    spawnFn,
    probeVersion: async () => {},
  };
}

test("ClaudeCodeAdapter.create returns claude-code sdk_agent_id", async () => {
  const adapter = new ClaudeCodeAdapter(baseAdapterOpts(() => mockChildProcess({})));
  const { sdk_agent_id } = await adapter.create(baseCreateSpec());
  assert.equal(sdk_agent_id, "claude-code:DEV-01");
});

test("ClaudeCodeAdapter.list returns empty array", async () => {
  const adapter = new ClaudeCodeAdapter(baseAdapterOpts(() => mockChildProcess({})));
  assert.deepEqual(await adapter.list(), []);
});

test("ClaudeCodeAdapter.listModels returns configured models", async () => {
  const adapter = new ClaudeCodeAdapter(baseAdapterOpts(() => mockChildProcess({})));
  const models = await adapter.listModels();
  assert.ok(models.includes("anthropic/claude-sonnet-4.5"));
  assert.ok(models.includes("~anthropic/claude-haiku-latest"));
});

test("ClaudeCodeAdapter.send spawns claude with OpenRouter env", async () => {
  let captured: {
    command?: string;
    args?: readonly string[];
    env?: NodeJS.ProcessEnv;
    cwd?: string;
  } = {};

  const adapter = new ClaudeCodeAdapter(
    baseAdapterOpts((command, args, options) => {
      captured = {
        command,
        args,
        env: options.env,
        cwd: typeof options.cwd === "string" ? options.cwd : undefined,
      };
      return mockChildProcess({
        stdout: JSON.stringify({ result: "ok", is_error: false }),
        exitCode: 0,
      });
    }),
  );

  const handle = await adapter.send(
    {
      sessionId: "sess-1",
      agentId: "DEV-01",
      text: "Say hi",
      modelId: "custom/sonnet-model",
    },
    "claude-code:DEV-01",
  );

  await new Promise((r) => setImmediate(r));

  assert.equal(captured.command, "claude");
  assert.deepEqual(captured.args, ["-p", "Say hi", "--output-format", "json"]);
  assert.equal(captured.cwd, "D:\\proj");
  assert.equal(captured.env?.ANTHROPIC_BASE_URL, "https://openrouter.ai/api");
  assert.equal(captured.env?.ANTHROPIC_AUTH_TOKEN, "sk-or-test");
  assert.equal(captured.env?.ANTHROPIC_API_KEY, "");
  assert.equal(captured.env?.ANTHROPIC_DEFAULT_SONNET_MODEL, "custom/sonnet-model");

  const run = await handle.whenSettled();
  assert.equal(run.status, "finished");
});

test("ClaudeCodeAdapter rejects missing authToken", () => {
  assert.throws(
    () => new ClaudeCodeAdapter({ authToken: "  " }),
    /requires authToken/,
  );
});

test("ClaudeCodeAdapter.resume rejects unexpected sdk_agent_id", async () => {
  const adapter = new ClaudeCodeAdapter(baseAdapterOpts(() => mockChildProcess({})));
  await assert.rejects(
    () => adapter.resume("cursor:abc"),
    /unexpected sdk_agent_id/,
  );
});
