import { spawn, type ChildProcessWithoutNullStreams, type SpawnOptions } from "node:child_process";
import type { AgentSdkAdapter, AgentCreateSpec, AgentSendSpec } from "./AgentSdkAdapter.ts";
import type { RunHandle } from "../types/state.ts";
import { ClaudeCodeRunHandle } from "../session/ClaudeCodeRunHandle.ts";

export type ClaudeSpawnFn = (
  command: string,
  args: readonly string[],
  options: SpawnOptions,
) => ChildProcessWithoutNullStreams;

export type ClaudeVersionProbe = (
  command: string,
  env: NodeJS.ProcessEnv,
) => Promise<void>;

export interface ClaudeCodeAdapterOptions {
  /** Working directory for `claude -p` (defaults to `process.cwd()`). */
  defaultCwd?: string;
  /** CLI binary name/path (default `"claude"`). */
  command?: string;
  baseUrl?: string;
  authToken: string;
  sonnetModel?: string;
  haikuModel?: string;
  subagentModel?: string;
  /** Test hook — replace `spawn` for unit tests. */
  spawnFn?: ClaudeSpawnFn;
  /** Test hook — replace `claude --version` probe. */
  probeVersion?: ClaudeVersionProbe;
}

const DEFAULT_BASE_URL = "https://openrouter.ai/api";
const DEFAULT_SONNET = "anthropic/claude-sonnet-4.5";
const DEFAULT_HAIKU = "~anthropic/claude-haiku-latest";
const DEFAULT_SUBAGENT = "~anthropic/claude-haiku-latest";

function defaultVersionProbe(
  command: string,
  env: NodeJS.ProcessEnv,
): Promise<void> {
  return new Promise((resolve, reject) => {
    const child = spawn(command, ["--version"], {
      env,
      shell: false,
      stdio: ["ignore", "pipe", "pipe"],
    });
    let stderr = "";
    child.stderr.on("data", (chunk: Buffer | string) => {
      stderr += chunk.toString();
    });
    child.on("error", (err) => {
      reject(
        new Error(
          `Claude Code CLI not found (${command}): ${err.message}. Install Claude Code and ensure it is on PATH.`,
        ),
      );
    });
    child.on("close", (code) => {
      if (code === 0) {
        resolve();
        return;
      }
      reject(
        new Error(
          `Claude Code CLI probe failed (${command} --version exit ${code ?? "unknown"}): ${stderr.trim() || "no stderr"}`,
        ),
      );
    });
  });
}

/**
 * ClaudeCodeAdapter — global `claude -p --output-format json` backend via OpenRouter.
 *
 * Maps CodeFlowMu's `AgentSdkAdapter` contract onto the Claude Code CLI with
 * ANTHROPIC_* env vars pointed at OpenRouter (see adapter plan §4 / §14).
 */
export class ClaudeCodeAdapter implements AgentSdkAdapter {
  private readonly _opts: Required<
    Pick<
      ClaudeCodeAdapterOptions,
      | "defaultCwd"
      | "command"
      | "baseUrl"
      | "authToken"
      | "sonnetModel"
      | "haikuModel"
      | "subagentModel"
    >
  > & {
    spawnFn: ClaudeSpawnFn;
    probeVersion: ClaudeVersionProbe;
  };

  constructor(opts: ClaudeCodeAdapterOptions) {
    if (!opts.authToken?.trim()) {
      throw new Error("ClaudeCodeAdapter requires authToken (ANTHROPIC_AUTH_TOKEN)");
    }
    this._opts = {
      defaultCwd: opts.defaultCwd ?? process.cwd(),
      command: opts.command ?? "claude",
      baseUrl: opts.baseUrl ?? DEFAULT_BASE_URL,
      authToken: opts.authToken,
      sonnetModel: opts.sonnetModel ?? DEFAULT_SONNET,
      haikuModel: opts.haikuModel ?? DEFAULT_HAIKU,
      subagentModel: opts.subagentModel ?? DEFAULT_SUBAGENT,
      spawnFn:
        opts.spawnFn ??
        ((command, args, options) =>
          spawn(command, [...args], options) as ChildProcessWithoutNullStreams),
      probeVersion: opts.probeVersion ?? defaultVersionProbe,
    };
  }

  private _buildEnv(modelOverride?: string): NodeJS.ProcessEnv {
    const sonnet = modelOverride?.trim() || this._opts.sonnetModel;
    return {
      ...process.env,
      ANTHROPIC_BASE_URL: this._opts.baseUrl,
      ANTHROPIC_AUTH_TOKEN: this._opts.authToken,
      ANTHROPIC_API_KEY: "",
      ANTHROPIC_DEFAULT_SONNET_MODEL: sonnet,
      ANTHROPIC_DEFAULT_HAIKU_MODEL: this._opts.haikuModel,
      CLAUDE_CODE_SUBAGENT_MODEL: this._opts.subagentModel,
    };
  }

  async create(spec: AgentCreateSpec): Promise<{ sdk_agent_id: string }> {
    void spec;
    return { sdk_agent_id: `claude-code:${spec.agentId}` };
  }

  async list(): Promise<string[]> {
    return [];
  }

  async resume(sdkAgentId: string): Promise<void> {
    if (!sdkAgentId.startsWith("claude-code:")) {
      throw new Error(
        `ClaudeCodeAdapter.resume: unexpected sdk_agent_id="${sdkAgentId}" (expected claude-code:<agentId>)`,
      );
    }
    await this._opts.probeVersion(this._opts.command, this._buildEnv());
  }

  async send(spec: AgentSendSpec, sdkAgentId: string): Promise<RunHandle> {
    if (!sdkAgentId.startsWith("claude-code:")) {
      throw new Error(
        `ClaudeCodeAdapter.send: unexpected sdk_agent_id="${sdkAgentId}"`,
      );
    }

    const handle = new ClaudeCodeRunHandle({
      sessionId: spec.sessionId,
      agentId: spec.agentId,
      maxToolRounds: spec.maxToolRounds,
      uiLang: spec.uiLang,
    });

    const args = ["-p", spec.text, "--output-format", "json"];
    const env = this._buildEnv(spec.modelId);
    const cwd = this._opts.defaultCwd;

    setImmediate(() => {
      try {
        const child = this._opts.spawnFn(this._opts.command, args, {
          cwd,
          env,
          shell: false,
          stdio: ["ignore", "pipe", "pipe"],
        });
        handle.attachProcess(child);
      } catch (err) {
        handle._finalizeFromOutputForTest(
          "",
          err instanceof Error ? err.message : String(err),
          1,
        );
      }
    });

    return handle;
  }

  async listModels(): Promise<string[]> {
    const models = [
      this._opts.sonnetModel,
      this._opts.haikuModel,
      this._opts.subagentModel,
    ];
    return [...new Set(models)];
  }
}
