import path from 'node:path';
import { randomUUID } from 'node:crypto';

import type { NormalizedAgentEvent } from '../agent-events';
import { normalizeClaudeJsonLine } from '../agent-events';
import { env } from '../env';
import type { AdapterFactory, TurnResult } from './agent-runtime';
import { writeJsonFile } from './auth';
import {
  buildMarkedCommand,
  killBoxProcessesByMarker,
  streamExecInContainer,
} from './docker';
import type { AdapterWorkspace } from './provider';
import { claudeEnv, claudeModel, isClaudeOAuthProfile } from './provider';

export const createClaudeAdapter: AdapterFactory = (deps) => {
  const execStream = deps?.execStream ?? streamExecInContainer;

  const prepareAuth = async (workspace: AdapterWorkspace) => {
    // API-key profiles authenticate purely via ANTHROPIC_API_KEY in the
    // environment; only OAuth-json profiles need `.credentials.json` on disk.
    if (!isClaudeOAuthProfile(workspace.claim)) return;
    const secret = workspace.claim.aiProviderProfile?.secret;
    if (!secret) {
      throw new Error('Claude auth profile is missing credentials.json contents.');
    }
    const credentialsPath = path.join(
      workspace.homeDir,
      '.claude',
      '.credentials.json',
    );
    await writeJsonFile(credentialsPath, secret, 'Claude auth');
  };

  const runTurn = async (
    workspace: AdapterWorkspace,
    prompt: string,
    onEvent: (event: NormalizedAgentEvent) => Promise<void>,
  ): Promise<TurnResult> => {
    workspace.runtime = 'claude';
    workspace.turnMarker = `spoon-turn-${randomUUID()}`;
    const marker = workspace.turnMarker;

    // Flags verified against `claude --help` (@anthropic-ai/claude-code@2.1.207):
    // `-p`, `--output-format stream-json`, `--verbose`, `--dangerously-skip-permissions`,
    // `--model`, and `--resume <sessionId>` (continuity) all exist.
    const argv = [
      'claude',
      '-p',
      prompt,
      '--output-format',
      'stream-json',
      '--verbose',
      '--dangerously-skip-permissions',
      '--model',
      claudeModel(workspace.claim),
      ...(workspace.claudeSessionId
        ? ['--resume', workspace.claudeSessionId]
        : []),
    ];
    const command = buildMarkedCommand(marker, argv);

    const aiEnv = claudeEnv(workspace.claim, workspace.containerHome);
    const secretEnv = Object.fromEntries(
      workspace.claim.secrets.map((secret) => [secret.name, secret.value]),
    );

    let capturedSessionId: string | undefined;
    let capturedError: string | undefined;
    let finalMessage: string | undefined;

    // execa's own `timeout` only kills the local docker-exec client, not the
    // in-box process group. Schedule an explicit marker kill on timeout and clear
    // it once the exec resolves.
    const killTimer = setTimeout(() => {
      void killBoxProcessesByMarker({
        containerName: workspace.boxName,
        marker,
      });
    }, env.jobTimeoutMs);

    let result;
    try {
      result = await execStream({
        containerName: workspace.boxName,
        containerCwd: workspace.containerRepo,
        command,
        environment: { ...aiEnv, ...secretEnv },
        redact: workspace.redact,
        timeoutMs: env.jobTimeoutMs,
        onStdoutLine: async (line) => {
          for (const event of normalizeClaudeJsonLine(line)) {
            if (event.kind === 'session') capturedSessionId = event.sessionId;
            if (event.kind === 'error') capturedError = event.message;
            if (event.kind === 'assistant_completed' && event.content) {
              finalMessage = event.content;
            }
            await onEvent(event);
          }
        },
        onStderrLine: async (line) => {
          if (!line.trim()) return;
          await onEvent({ kind: 'status', status: line });
        },
      });
    } finally {
      clearTimeout(killTimer);
    }

    if (result.exitCode !== 0) {
      capturedError = result.output;
    }

    return { finalMessage, sessionId: capturedSessionId, error: capturedError };
  };

  const abort = async (workspace: AdapterWorkspace) => {
    if (!workspace.turnMarker) return;
    await killBoxProcessesByMarker({
      containerName: workspace.boxName,
      marker: workspace.turnMarker,
    });
  };

  return { name: 'claude', prepareAuth, runTurn, abort };
};
