import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { execSync } from "node:child_process";

const __dirname = dirname(fileURLToPath(import.meta.url));
const FRAMEWORK_ROOT = join(__dirname, "..");
const MANIFEST_PATH = join(FRAMEWORK_ROOT, "config", "manifest.json");

function run(cmd, opts = {}) {
  try {
    return execSync(cmd, { stdio: "pipe", ...opts }).toString().trim();
  } catch {
    return null;
  }
}

/**
 * Merge per-module ecosystem configs into a single combined config at
 * {systemConfig.installPath}/ecosystem.config.json.
 */
export function generatePm2EcosystemConfig(systemConfig, installedModules) {
  const apps = [];
  for (const [, info] of Object.entries(installedModules)) {
    const ecoCfg = join(info.installPath, "ecosystem.config.json");
    if (!existsSync(ecoCfg)) continue;
    try {
      const cfg = JSON.parse(readFileSync(ecoCfg, "utf8"));
      apps.push(...(cfg.apps ?? []));
    } catch {
      // Skip malformed configs
    }
  }
  const combined = { apps };
  const outputPath = join(systemConfig.installPath, "ecosystem.config.json");
  writeFileSync(outputPath, JSON.stringify(combined, null, 2));
  console.log(`[post-install] Combined PM2 config → ${outputPath}`);
  return outputPath;
}

/**
 * Configure PM2 to start on boot via launchd (macOS). Prints the sudo command
 * if it needs to be run manually.
 *
 * Homebrew's bin directory is not in the launchd PATH, so we resolve the full
 * pm2 binary path and inject the Homebrew bin dir into PATH before running
 * pm2 startup. Failures are non-fatal — the user is told how to run it manually.
 */
export function setupPm2Startup() {
  console.log("[post-install] Configuring PM2 startup (launchd)...");

  // Find the full path to pm2 — Homebrew PATH is absent in launchd environments
  let pm2Bin = run("which pm2");
  if (!pm2Bin) {
    const brewPrefix = run("brew --prefix");
    pm2Bin = brewPrefix ? join(brewPrefix, "bin", "pm2") : "/opt/homebrew/bin/pm2";
  }
  if (!pm2Bin.startsWith("/")) {
    console.log("[post-install] Warning: Could not locate pm2 binary. Skipping startup config.");
    console.log("[post-install] To enable auto-start, run manually: pm2 startup launchd");
    return;
  }

  // Build a PATH that includes Homebrew so pm2 can find node/npm at boot
  const brewPrefix = run("brew --prefix") ?? "/opt/homebrew";
  const envPath = [
    join(brewPrefix, "bin"),
    "/usr/local/bin",
    "/usr/bin",
    "/bin",
    "/usr/sbin",
    "/sbin",
    process.env.PATH ?? "",
  ].join(":");

  const output = run(`"${pm2Bin}" startup launchd`, {
    env: { ...process.env, PATH: envPath },
  });
  if (!output) {
    console.log("[post-install] Warning: pm2 startup failed — run it manually:");
    console.log(`  ${pm2Bin} startup launchd`);
    return;
  }
  const sudoLine = output.split("\n").find(l => l.trimStart().startsWith("sudo"));
  if (sudoLine) {
    console.log("[post-install] Run this command to enable auto-start:");
    console.log(`  ${sudoLine.trim()}`);
  }
  run(`"${pm2Bin}" save`);
  console.log("[post-install] PM2 process list saved.");
}

/**
 * Generate a PM2 ecosystem config for the conductor agent and start it.
 * Warns that Claude Code auth is required before the agent goes online.
 */
export function startConductorAgent(systemConfig) {
  const systemNameLower = systemConfig.name.toLowerCase().replace(/\s+/g, "-");
  const conductorDest = join(systemConfig.installPath, "agents", systemNameLower);
  const startSh = join(conductorDest, "start.sh");

  if (!existsSync(startSh)) {
    console.log("[post-install] Warning: Conductor start.sh not found, skipping agent start.");
    return;
  }

  if (systemConfig.claudeBinaryPath) {
    console.log(`[post-install] Claude binary: ${systemConfig.claudeBinaryPath}`);
  } else {
    console.log("[post-install] Warning: claudeBinaryPath not set in system.json — agent start.sh may not find the claude binary.");
  }

  const conductorServiceName = `${systemNameLower}-agent`;
  const conductorEco = join(conductorDest, "ecosystem.config.cjs");

  let pm2Bin = run("which pm2");
  if (!pm2Bin) {
    const brewPrefix = run("brew --prefix");
    pm2Bin = brewPrefix ? join(brewPrefix, "bin", "pm2") : "/opt/homebrew/bin/pm2";
  }

  writeFileSync(conductorEco, `module.exports = {
  apps: [{
    name: "${conductorServiceName}",
    script: "${startSh}",
    interpreter: "bash",
    cwd: "${conductorDest}",
    restart_delay: 5000,
    max_restarts: 50,
  }]
};\n`);

  try {
    execSync(`"${pm2Bin}" start "${conductorEco}"`, { stdio: "inherit" });
    run(`"${pm2Bin}" save`);
    console.log(`[post-install] Conductor PM2 service started: ${conductorServiceName}`);
  } catch (err) {
    console.warn(`[post-install] Warning: could not start conductor agent: ${err.message}`);
  }

  console.log("[post-install] Warning: Claude Code auth is needed for the agent to come online. Run 'claude' to authenticate.");
}

/**
 * Curl the /health endpoint for each module that has a known port.
 * Returns [{name, port, healthy, response}].
 */
export async function runHealthChecks(installedModules) {
  console.log("\n[post-install] Running health checks...");
  const results = [];
  for (const [name, info] of Object.entries(installedModules)) {
    const port = (info.ports ?? [])[0];
    if (!port) continue;
    const response = run(`curl -sf --max-time 5 http://localhost:${port}/health`);
    const healthy = response !== null;
    results.push({ name, port, healthy, response: response ?? undefined });
    const icon = healthy ? "✓" : "✗";
    console.log(`  ${icon} ${name} — port ${port} — ${healthy ? "healthy" : "not responding"}`);
  }
  return results;
}

/**
 * Print a human-readable install summary.
 */
export function printSummary(systemConfig, installedModules, healthResults = []) {
  const modNames = Object.keys(installedModules);
  const healthy = healthResults.filter(r => r.healthy).length;
  const checked = healthResults.length;

  console.log("\n═══════════════════════════════════════════════════════════════");
  console.log("  POST-INSTALL SUMMARY");
  console.log("═══════════════════════════════════════════════════════════════");
  console.log(`  System:      ${systemConfig.name}`);
  console.log(`  Install dir: ${systemConfig.installPath}`);
  console.log(`  Modules:     ${modNames.length} installed`);
  if (checked > 0) console.log(`  Health:      ${healthy}/${checked} services responding`);

  if (modNames.length > 0) {
    console.log("\n  Installed modules:");
    for (const [name, info] of Object.entries(installedModules)) {
      const hc = healthResults.find(r => r.name === name);
      const icon = hc ? (hc.healthy ? "✓" : "✗") : "–";
      console.log(`    ${icon} ${name} v${info.version ?? "?"} → ${info.installPath}`);
    }
  }

  console.log("\n  Next steps:");
  console.log("    pm2 status               # Check all services");
  console.log("    pm2 logs                 # View service logs");
  console.log(`    cat ${systemConfig.installPath}/config/system.json`);
  console.log("═══════════════════════════════════════════════════════════════\n");
}

// Allow running directly: node installer/post-install.js [path/to/system.json]
if (process.argv[1] === fileURLToPath(import.meta.url)) {
  const systemConfigPath = process.argv[2] ?? join(process.cwd(), "config", "system.json");
  if (!existsSync(systemConfigPath)) {
    console.error(`system.json not found at ${systemConfigPath}`);
    process.exit(1);
  }
  const systemConfig = JSON.parse(readFileSync(systemConfigPath, "utf8"));
  const manifest = JSON.parse(readFileSync(MANIFEST_PATH, "utf8"));
  const installedModules = manifest.modules ?? {};

  startConductorAgent(systemConfig);
  generatePm2EcosystemConfig(systemConfig, installedModules);
  setupPm2Startup();
  const healthResults = await runHealthChecks(installedModules);
  printSummary(systemConfig, installedModules, healthResults);
}
