#!/usr/bin/env node
import { execFileSync, spawnSync } from 'node:child_process';

const DRY_RUN = process.argv.includes('--dry-run');
const FD_WARN = Number(process.env.GOOSED_FD_WARN ?? 180);
const FD_RESTART = Number(process.env.GOOSED_FD_RESTART ?? 3200);
const SANDBOX_MAX_TOTAL = Number(process.env.SANDBOX_MCP_MAX_TOTAL ?? 80);
const SANDBOX_MAX_PER_ROOT = Number(process.env.SANDBOX_MCP_MAX_PER_ROOT ?? 2);

function sh(command) {
  return execFileSync('/bin/zsh', ['-lc', command], { encoding: 'utf8' }).trim();
}

function run(command, args) {
  if (DRY_RUN) {
    console.log(`[dry-run] ${command} ${args.join(' ')}`);
    return { status: 0 };
  }
  return spawnSync(command, args, { stdio: 'inherit' });
}

function psLines(pattern) {
  const output = sh(
    `ps -axo pid=,ppid=,etime=,command= | grep ${JSON.stringify(pattern)} | grep -v grep || true`,
  );
  if (!output) return [];
  return output.split('\n').map((line) => {
    const match = line.trim().match(/^(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/);
    if (!match) return null;
    return {
      pid: Number(match[1]),
      ppid: Number(match[2]),
      etimes: etimeToSeconds(match[3]),
      command: match[4],
    };
  }).filter(Boolean);
}

function etimeToSeconds(value) {
  const parts = String(value ?? '').split('-');
  const dayPart = parts.length === 2 ? Number(parts[0]) : 0;
  const timePart = parts.at(-1) ?? '0';
  const nums = timePart.split(':').map((item) => Number(item));
  if (nums.some((item) => !Number.isFinite(item))) return 0;
  const [hours, minutes, seconds] =
    nums.length === 3 ? nums : nums.length === 2 ? [0, nums[0], nums[1]] : [0, 0, nums[0]];
  return dayPart * 86400 + hours * 3600 + minutes * 60 + seconds;
}

function fdCount(pid) {
  const count = sh(`lsof -n -p ${pid} 2>/dev/null | wc -l || true`);
  return Number(count.trim() || 0);
}

function killPid(pid, reason) {
  console.log(`kill pid=${pid} reason=${reason}`);
  run('/bin/kill', ['-TERM', String(pid)]);
}

function rootForSandbox(command) {
  const marker = 'mindspace-sandbox-mcp.mjs ';
  const idx = command.indexOf(marker);
  if (idx < 0) return '';
  return command.slice(idx + marker.length).trim().split(/\s+/)[0] ?? '';
}

function cleanupSandboxProcesses() {
  const processes = psLines('mindspace-sandbox-mcp.mjs');
  const byParentRoot = new Map();
  for (const proc of processes) {
    const root = rootForSandbox(proc.command);
    const key = `${proc.ppid}:${root}`;
    if (!byParentRoot.has(key)) byParentRoot.set(key, []);
    byParentRoot.get(key).push(proc);
  }

  const toKill = new Map();
  for (const group of byParentRoot.values()) {
    group.sort((a, b) => b.etimes - a.etimes);
    for (const proc of group.slice(0, Math.max(0, group.length - SANDBOX_MAX_PER_ROOT))) {
      toKill.set(proc.pid, { proc, reason: 'duplicate-sandbox-root' });
    }
  }
  for (const proc of processes) {
    const parentAlive = proc.ppid > 1 && psLines(`^${proc.ppid} `).length > 0;
    if (!parentAlive && proc.etimes > 60) {
      toKill.set(proc.pid, { proc, reason: 'orphan-sandbox' });
    }
  }
  if (processes.length > SANDBOX_MAX_TOTAL) {
    const oldest = [...processes].sort((a, b) => b.etimes - a.etimes);
    for (const proc of oldest.slice(0, processes.length - SANDBOX_MAX_TOTAL)) {
      toKill.set(proc.pid, { proc, reason: `over-total-${SANDBOX_MAX_TOTAL}` });
    }
  }

  for (const { proc, reason } of toKill.values()) {
    killPid(proc.pid, reason);
  }
  console.log(`sandbox processes=${processes.length} killed=${toKill.size}`);
}

function goosedLabel(command) {
  const port = command.match(/\b(18006|18007)\b/)?.[1];
  return port ? `cn.tkmind.goosed-${port}` : null;
}

function monitorGoosed() {
  const processes = psLines('goosed agent');
  for (const proc of processes) {
    const fds = fdCount(proc.pid);
    console.log(`goosed pid=${proc.pid} fd=${fds} cmd=${proc.command}`);
    if (fds >= FD_RESTART) {
      const label = goosedLabel(proc.command);
      if (label) {
        console.log(`restart ${label} fd=${fds} threshold=${FD_RESTART}`);
        run('/bin/launchctl', ['kickstart', '-k', `gui/${process.getuid()}/${label}`]);
      }
    } else if (fds >= FD_WARN) {
      console.warn(`warn goosed pid=${proc.pid} fd=${fds} threshold=${FD_WARN}`);
    }
  }
}

cleanupSandboxProcesses();
monitorGoosed();
