import { spawn } from 'node:child_process';
import { getDb, newId, nowIso } from '../../db.js';
import { appendAudit } from '../../runtime.js';
import { createApprovalRequest, consumeApprovalRequest } from '../../approvals.js';
import { classifyCommand, redactToolOutput, resolveSafePath } from '../safety.js';

const children = new Map();

function cap(text) {
  return redactToolOutput(String(text || '').slice(0, Number(process.env.MADARX_TOOL_OUTPUT_MAX_BYTES || 50000)));
}

function sameArgs(left, right) {
  return JSON.stringify(left || []) === JSON.stringify(right || []);
}

function row(id) {
  return getDb().prepare('SELECT * FROM local_processes WHERE id = ?').get(id);
}

function update(id, fields) {
  const entries = Object.entries(fields);
  if (!entries.length) return row(id);
  const sets = entries.map(([key]) => `${key} = ?`).join(', ');
  getDb().prepare(`UPDATE local_processes SET ${sets}, updated_at = ? WHERE id = ?`)
    .run(...entries.map(([, value]) => value), nowIso(), id);
  return row(id);
}

export function processStartPreflight(input = {}) {
  const command = String(input.command || '');
  const args = Array.isArray(input.args) ? input.args.map(String) : [];
  const { path: cwd } = resolveSafePath(input.cwd || '.');
  const classified = classifyCommand(command, args);
  if (classified.blocked) throw new Error(classified.reason);
  return { command, args, cwd, classified };
}

export async function processStart(input = {}, options = {}) {
  const { command, args, cwd, classified } = processStartPreflight(input);
  if (classified.approval_required && !options.owner_trusted && !consumeApprovalRequest(input.approval_id, {
    action_type: 'process_start',
    matches: payload => payload.command === command && sameArgs(payload.args, args) && payload.cwd === cwd,
  })) {
    const req = createApprovalRequest({ action_type: 'process_start', risk: classified.risk, summary: `Start process ${command}`, payload: { command, args, cwd } });
    appendAudit({ type: 'local.process_start', summary: `process_start approval required: ${command}`, status: 'approval_required', meta: { command, args, cwd, approval_request_id: req.id } });
    return { ok: false, status: 'approval_required', approval_request_id: req.id, reason: classified.reason };
  }
  const id = newId('proc');
  const now = nowIso();
  const child = spawn(command, args, { cwd, shell: false, stdio: ['ignore', 'pipe', 'pipe'] });
  const state = { stdout: '', stderr: '' };
  children.set(id, { child, state });
  getDb().prepare(`
    INSERT INTO local_processes (id, pid, command, args, cwd, status, risk, stdout_preview, stderr_preview, started_at, updated_at)
    VALUES (?, ?, ?, ?, ?, 'running', ?, '', '', ?, ?)
  `).run(id, child.pid, command, JSON.stringify(args), cwd, classified.risk, now, now);
  child.stdout.on('data', chunk => {
    state.stdout = cap(state.stdout + chunk);
    update(id, { stdout_preview: state.stdout });
  });
  child.stderr.on('data', chunk => {
    state.stderr = cap(state.stderr + chunk);
    update(id, { stderr_preview: state.stderr });
  });
  child.on('close', code => {
    update(id, { status: code === 0 ? 'complete' : 'failed', exit_code: code, completed_at: nowIso(), stdout_preview: state.stdout, stderr_preview: state.stderr });
    appendAudit({ type: 'local.process_start', summary: `process complete: ${command}`, status: code === 0 ? 'complete' : 'failed', meta: { process_id: id, pid: child.pid, command, args, cwd, exit_code: code, stdout_preview: state.stdout, stderr_preview: state.stderr } });
    children.delete(id);
  });
  child.on('error', error => {
    update(id, { status: 'failed', stderr_preview: cap(error.message), completed_at: nowIso() });
    appendAudit({ type: 'local.process_start', summary: `process failed: ${command}`, status: 'failed', meta: { process_id: id, pid: child.pid, command, args, cwd, error: error.message } });
    children.delete(id);
  });
  appendAudit({ type: 'local.process_start', summary: `process started: ${command}`, status: 'running', meta: { process_id: id, pid: child.pid, command, args, cwd, risk: classified.risk } });
  return { ok: true, tool: 'process_start', process_id: id, pid: child.pid, status: 'running' };
}

export async function processPoll(input = {}) {
  const proc = row(input.process_id);
  if (!proc) throw new Error('Process not found');
  appendAudit({ type: 'local.process_poll', summary: `process_poll ${proc.status}: ${proc.command}`, status: proc.status, meta: { process_id: proc.id, pid: proc.pid } });
  return { ok: true, tool: 'process_poll', process: proc };
}

export function processKillPreflight(input = {}) {
  const proc = row(input.process_id);
  if (!proc) throw new Error('Process not found');
  return { proc };
}

export async function processKill(input = {}, options = {}) {
  const { proc } = processKillPreflight(input);
  if (proc.status !== 'running') return { ok: true, tool: 'process_kill', process: proc };
  if (!options.owner_trusted && !consumeApprovalRequest(input.approval_id, {
    action_type: 'process_kill',
    matches: payload => payload.process_id === proc.id && Number(payload.pid) === Number(proc.pid),
  })) {
    const req = createApprovalRequest({ action_type: 'process_kill', risk: 'high', summary: `Kill process ${proc.command}`, payload: { process_id: proc.id, pid: proc.pid, command: proc.command } });
    appendAudit({ type: 'local.process_kill', summary: `process_kill approval required: ${proc.command}`, status: 'approval_required', meta: { process_id: proc.id, pid: proc.pid, approval_request_id: req.id } });
    return { ok: false, status: 'approval_required', approval_request_id: req.id, reason: 'Process kill requires approval' };
  }
  const tracked = children.get(proc.id);
  if (!tracked) throw new Error('Only madarX-owned live processes can be killed');
  tracked.child.kill('SIGTERM');
  const next = update(proc.id, { status: 'killed', completed_at: nowIso() });
  appendAudit({ type: 'local.process_kill', summary: `process killed: ${proc.command}`, status: 'killed', meta: { process_id: proc.id, pid: proc.pid } });
  children.delete(proc.id);
  return { ok: true, tool: 'process_kill', process: next };
}
