#!/usr/bin/env node
import { spawn } from 'node:child_process';
import { buildHermesChildEnv, buildHermesCommandArgs, parseJsonOutput } from '../lib/hermes.js';
import { buildHermesArtifactPrompt, enforceHermesArtifactFormat } from '../lib/hermes-format.js';
import {
  appendHermesJobEvent,
  completeHermesJob,
  failHermesJob,
  getHermesJob,
  markHermesJobRunning,
} from '../lib/hermes-jobs.js';
import { resetDbHandle } from '../lib/db.js';

function argValue(name) {
  const idx = process.argv.indexOf(name);
  return idx === -1 ? null : process.argv[idx + 1] || null;
}

function buildPrompt(job) {
  return buildHermesArtifactPrompt(`${job.prompt}

If a dev server is needed, start it only long enough to capture the useful output, then stop it or report the exact command and URL inside the artifact body.`);
}

async function main() {
  const jobId = argValue('--job-id');
  if (!jobId) throw new Error('--job-id is required');

  const job = getHermesJob(jobId);
  if (!job) throw new Error(`Hermes job not found: ${jobId}`);
  if (!['queued', 'running'].includes(job.status)) {
    appendHermesJobEvent(jobId, { type: 'status', message: `Runner skipped job with status ${job.status}.` });
    return;
  }

  if (process.env.HERMES_MOCK_RESPONSE) {
    markHermesJobRunning(jobId, { pid: process.pid });
    appendHermesJobEvent(jobId, { type: 'stdout', message: 'Using HERMES_MOCK_RESPONSE.' });
    const formatted = enforceHermesArtifactFormat(process.env.HERMES_MOCK_RESPONSE, { title: job.title });
    if (!formatted.ok) appendHermesJobEvent(jobId, { type: 'format_violation', message: formatted.violations.join(', '), payload: formatted });
    completeHermesJob(jobId, {
      response: formatted.content,
      model: job.model,
      provider: 'mock',
      hermes_session_id: job.hermes_session_id || 'madarx-hermes-mock',
      format_source: formatted.source,
      format_violations: formatted.violations,
    });
    return;
  }

  const { hermesDir, python, env } = buildHermesChildEnv();
  const args = buildHermesCommandArgs({
    prompt: buildPrompt(job),
    session_id: job.hermes_session_id,
    model: job.model,
    cwd: job.cwd,
  });

  markHermesJobRunning(jobId, { pid: process.pid });
  appendHermesJobEvent(jobId, {
    type: 'status',
    message: `Launching Hermes with ${python}.`,
    payload: { cwd: job.cwd || hermesDir },
  });

  const child = spawn(python, args, {
    cwd: hermesDir,
    env,
    stdio: ['ignore', 'pipe', 'pipe'],
  });

  let stdout = '';
  let stderr = '';
  let timedOut = false;
  const timeoutMs = Number(job.timeout_ms || process.env.HERMES_JOB_TIMEOUT_MS || 30 * 60 * 1000);
  const timer = setTimeout(() => {
    timedOut = true;
    appendHermesJobEvent(jobId, { type: 'status', message: `Hermes job timed out after ${Math.round(timeoutMs / 1000)}s; stopping child process.` });
    child.kill('SIGTERM');
  }, timeoutMs);

  child.stdout.on('data', chunk => {
    const text = chunk.toString();
    stdout += text;
    for (const line of text.split(/\r?\n/).filter(Boolean)) {
      if (line.trim().startsWith('{') && line.includes('"ok"')) continue;
      appendHermesJobEvent(jobId, { type: 'stdout', message: line });
    }
  });

  child.stderr.on('data', chunk => {
    const text = chunk.toString();
    stderr += text;
    for (const line of text.split(/\r?\n/).filter(Boolean)) {
      appendHermesJobEvent(jobId, { type: 'stderr', message: line });
    }
  });

  const code = await new Promise(resolve => {
    child.on('error', error => resolve({ error }));
    child.on('close', exitCode => resolve({ exitCode }));
  });
  clearTimeout(timer);

  if (timedOut) {
    failHermesJob(jobId, { error: `Hermes job timed out after ${Math.round(timeoutMs / 1000)}s`, status: 'failed' });
    return;
  }

  if (code.error) {
    failHermesJob(jobId, { error: code.error.message });
    return;
  }

  if (code.exitCode !== 0) {
    failHermesJob(jobId, {
      exit_code: code.exitCode,
      error: (stderr || stdout || `Hermes exited with code ${code.exitCode}`).trim(),
    });
    return;
  }

  try {
    const result = parseJsonOutput(stdout);
    if (!result.ok) throw new Error(result.error || 'Hermes command failed');
    if (!String(result.response || '').trim()) throw new Error('Hermes completed without a final response');
    for (const event of result.events || []) {
      if (event.type === 'tool_start') appendHermesJobEvent(jobId, { type: 'tool_start', message: event.name, payload: event });
      else if (event.type === 'tool_result') appendHermesJobEvent(jobId, { type: 'tool_result', message: event.name, payload: event });
    }
    const formatted = enforceHermesArtifactFormat(result.response, { title: job.title });
    if (formatted.violations.length) {
      appendHermesJobEvent(jobId, {
        type: formatted.ok ? 'format_warning' : 'format_violation',
        message: formatted.violations.join(', '),
        payload: { source: formatted.source, violations: formatted.violations },
      });
    }
    completeHermesJob(jobId, {
      response: formatted.content,
      hermes_session_id: result.session_id,
      model: result.model,
      provider: result.provider,
      exit_code: code.exitCode,
      format_source: formatted.source,
      format_violations: formatted.violations,
    });
  } catch (error) {
    failHermesJob(jobId, { error: error.message, exit_code: code.exitCode });
  }
}

main()
  .catch(error => {
    const jobId = argValue('--job-id');
    if (jobId) {
      try { failHermesJob(jobId, { error: error.message }); } catch {}
    } else {
      console.error(error);
    }
    process.exitCode = 1;
  })
  .finally(() => resetDbHandle());
