#!/usr/bin/env node
'use strict';

const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { execFileSync, spawnSync } = require('node:child_process');

const { createClient } = require('../llm/client');
const orchestrator = require('../coding-orchestrator');

const DEFAULT_MODELS = {
  openai: 'gpt-4o-mini',
  deepseek: 'deepseek-v4-flash',
  moonshot: 'kimi-k2.6',
  google: 'gemini-2.5-flash',
  anthropic: 'claude-haiku-4-5-20251001',
};

function parseArgs(argv) {
  const args = {
    provider: process.env.WALLE_SMOKE_PROVIDER || 'openai',
    model: process.env.WALLE_SMOKE_MODEL || null,
    delivery: 'diff',
    keep: false,
    timeoutMs: 180000,
  };
  for (let i = 0; i < argv.length; i++) {
    const arg = argv[i];
    if (arg === '--jsonl') args.jsonl = argv[++i];
    else if (arg === '--provider') args.provider = argv[++i];
    else if (arg === '--model') args.model = argv[++i];
    else if (arg === '--delivery') args.delivery = argv[++i];
    else if (arg === '--timeout-ms') args.timeoutMs = Number(argv[++i]);
    else if (arg === '--keep') args.keep = true;
    else if (arg === '--help' || arg === '-h') args.help = true;
    else throw new Error(`Unknown argument: ${arg}`);
  }
  if (!args.model) args.model = DEFAULT_MODELS[args.provider] || DEFAULT_MODELS.openai;
  return args;
}

function usage() {
  return [
    'Usage: node scripts/smoke-coding-agent-jsonl.js --jsonl <session.jsonl> [--provider openai] [--model gpt-4o-mini] [--keep]',
    '',
    'Reconstructs a small coding task from a Claude/Codex JSONL session and runs the Wall-E coding agent with a real LLM provider.',
  ].join('\n');
}

function asText(content) {
  if (typeof content === 'string') return content;
  if (!Array.isArray(content)) return '';
  return content
    .map((part) => {
      if (typeof part === 'string') return part;
      if (part && typeof part.text === 'string') return part.text;
      if (part && typeof part.content === 'string') return part.content;
      return '';
    })
    .filter(Boolean)
    .join('\n');
}

function extractReplayCase(jsonlPath) {
  const raw = fs.readFileSync(jsonlPath, 'utf8');
  let cwd = null;
  let request = null;
  let sourceFile = null;

  for (const line of raw.split('\n')) {
    if (!line.trim()) continue;
    const entry = JSON.parse(line);
    if (!cwd && entry.cwd) cwd = entry.cwd;
    if (!request && entry.message?.role === 'user') {
      const text = asText(entry.message.content).trim();
      if (text && !text.startsWith('<command-')) request = text;
    }
    const result = entry.toolUseResult;
    if (!sourceFile && result?.originalFile && result?.filePath) {
      const absFile = path.resolve(result.filePath);
      const relPath = cwd && path.isAbsolute(absFile)
        ? path.relative(path.resolve(cwd), absFile)
        : path.basename(absFile);
      sourceFile = {
        relPath: relPath && !relPath.startsWith('..') ? relPath : path.basename(absFile),
        content: result.originalFile,
      };
    }
  }

  if (!request) throw new Error(`No user coding request found in ${jsonlPath}`);
  if (!sourceFile) throw new Error(`No toolUseResult.originalFile found in ${jsonlPath}`);
  return { cwd, request, sourceFile, sourceSession: path.basename(jsonlPath) };
}

function writeHealthTest(repoDir) {
  fs.writeFileSync(path.join(repoDir, 'test-health.js'), [
    "'use strict';",
    "const assert = require('node:assert/strict');",
    "const server = require('./server');",
    '',
    'let closed = false;',
    'function closeAndExit(code) {',
    '  if (closed) return;',
    '  closed = true;',
    '  server.close(() => process.exit(code));',
    '}',
    '',
    "server.listen(0, '127.0.0.1', async () => {",
    '  try {',
    '    const { port } = server.address();',
    "    const res = await fetch(`http://127.0.0.1:${port}/health`);",
    '    assert.equal(res.status, 200);',
    '    const body = await res.json();',
    "    assert.equal(body.status, 'ok');",
    "    assert.equal(typeof body.uptime, 'number');",
    '    closeAndExit(0);',
    '  } catch (err) {',
    '    console.error(err && err.stack ? err.stack : err);',
    '    closeAndExit(1);',
    '  }',
    '});',
    '',
    'setTimeout(() => {',
    "  console.error('health test timed out');",
    '  closeAndExit(1);',
    '}, 10000).unref();',
    '',
  ].join('\n'));
}

function initRepo(repoDir, replayCase) {
  const target = path.join(repoDir, replayCase.sourceFile.relPath);
  fs.mkdirSync(path.dirname(target), { recursive: true });
  fs.writeFileSync(target, replayCase.sourceFile.content);
  fs.writeFileSync(path.join(repoDir, 'package.json'), JSON.stringify({
    name: 'walle-jsonl-coding-smoke',
    private: true,
    scripts: { test: 'node test-health.js' },
  }, null, 2) + '\n');
  writeHealthTest(repoDir);

  execFileSync('git', ['init'], { cwd: repoDir, stdio: 'ignore' });
  execFileSync('git', ['config', 'user.email', 'walle-smoke@example.com'], { cwd: repoDir });
  execFileSync('git', ['config', 'user.name', 'Wall-E Smoke'], { cwd: repoDir });
  execFileSync('git', ['add', '.'], { cwd: repoDir });
  execFileSync('git', ['commit', '-m', 'fixture'], { cwd: repoDir, stdio: 'ignore' });
}

function run(repoDir, command, args) {
  return spawnSync(command, args, { cwd: repoDir, encoding: 'utf8' });
}

function createProvider(type) {
  if (type === 'openai') {
    if (!process.env.OPENAI_API_KEY) throw new Error('OPENAI_API_KEY is required for --provider openai');
    return createClient('openai', { apiKey: process.env.OPENAI_API_KEY, baseUrl: process.env.OPENAI_BASE_URL });
  }
  if (type === 'deepseek') {
    if (!process.env.DEEPSEEK_API_KEY) throw new Error('DEEPSEEK_API_KEY is required for --provider deepseek');
    return createClient('deepseek', { apiKey: process.env.DEEPSEEK_API_KEY, baseUrl: process.env.DEEPSEEK_BASE_URL });
  }
  if (type === 'moonshot') {
    if (!process.env.MOONSHOT_API_KEY) throw new Error('MOONSHOT_API_KEY is required for --provider moonshot');
    return createClient('moonshot', { apiKey: process.env.MOONSHOT_API_KEY, baseUrl: process.env.MOONSHOT_BASE_URL });
  }
  if (type === 'google') {
    const apiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
    if (!apiKey) throw new Error('GOOGLE_API_KEY or GEMINI_API_KEY is required for --provider google');
    return createClient('google', { apiKey });
  }
  if (type === 'anthropic') {
    if (!process.env.ANTHROPIC_API_KEY) throw new Error('ANTHROPIC_API_KEY is required for --provider anthropic');
    return createClient('anthropic', { apiKey: process.env.ANTHROPIC_API_KEY });
  }
  return createClient(type, {});
}

function printProgress(event) {
  const label = event.type || event.phase || 'progress';
  const detail = event.title || event.message || event.error || '';
  process.stderr.write(`[${label}] ${detail}\n`);
}

async function main() {
  const args = parseArgs(process.argv.slice(2));
  if (args.help || !args.jsonl) {
    console.log(usage());
    process.exit(args.help ? 0 : 2);
  }

  const replayCase = extractReplayCase(path.resolve(args.jsonl));
  const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'walle-coding-jsonl-smoke-'));
  let keep = args.keep;

  try {
    initRepo(repoDir, replayCase);
    const before = run(repoDir, process.execPath, ['test-health.js']);
    if (before.status === 0) {
      throw new Error('Fixture health test unexpectedly passed before the coding agent ran');
    }

    const provider = createProvider(args.provider);
    const request = replayCase.request;
    const planData = await orchestrator.plan(request, repoDir, {
      provider,
      model: args.model,
      max_subtasks: 1,
      max_retries: 1,
      total_budget_usd: 1,
      subtask_budget_usd: 1,
      subtask_timeout_ms: args.timeoutMs,
      test_command: 'node test-health.js',
      review: false,
      delivery: args.delivery,
      onProgress: printProgress,
    });
    planData.config.provider = provider;
    planData.config.model = args.model;

    const executeResult = await orchestrator.execute(planData, { cwd: repoDir, onProgress: printProgress });
    const report = await orchestrator.complete(request, planData, executeResult, { cwd: repoDir, onProgress: printProgress });
    const after = run(repoDir, process.execPath, ['test-health.js']);
    if (after.status !== 0) {
      keep = true;
      throw new Error(`Health test failed after agent run:\n${after.stdout || ''}${after.stderr || ''}`);
    }
    if (!report.success) {
      keep = true;
      throw new Error(`Coding agent report failed: ${report.error || 'unknown error'}`);
    }

    const diff = execFileSync('git', ['diff', '--', replayCase.sourceFile.relPath], {
      cwd: repoDir,
      encoding: 'utf8',
      maxBuffer: 1024 * 1024,
    });
    console.log(JSON.stringify({
      success: true,
      provider: args.provider,
      model: args.model,
      repoDir,
      sourceSession: replayCase.sourceSession,
      request,
      filesChanged: report.files_changed,
      diff,
    }, null, 2));
  } catch (err) {
    keep = true;
    console.error(err && err.stack ? err.stack : err);
    console.error(`Smoke repo kept at ${repoDir}`);
    process.exitCode = 1;
  } finally {
    if (!keep) fs.rmSync(repoDir, { recursive: true, force: true });
  }
}

main();
