#!/usr/bin/env node

import { spawn, execSync } from "child_process";
import { existsSync, readFileSync, writeFileSync, unlinkSync, openSync, cpSync } from "fs";
import { resolve } from "path";
import { platform } from "os";
import { createServer } from "net";
import { PROJECT_ROOT, DATA_DIR, PID_FILE, LOG_FILE, PORT, DASHBOARD_URL } from "./paths.mjs";

// ─── Helpers ────────────────────────────────────────────────────────────

const IS_WIN = platform() === "win32";
const GREEN = "\x1b[32m";
const YELLOW = "\x1b[33m";
const RED = "\x1b[31m";
const CYAN = "\x1b[36m";
const DIM = "\x1b[2m";
const BOLD = "\x1b[1m";
const RESET = "\x1b[0m";

function log(msg) { console.log(`${DIM}[arouter]${RESET} ${msg}`); }
function success(msg) { console.log(`${GREEN}✔${RESET} ${msg}`); }
function warn(msg) { console.log(`${YELLOW}⚠${RESET} ${msg}`); }
function error(msg) { console.error(`${RED}✖${RESET} ${msg}`); }

/** Cross-platform port check using Node.js net module */
function isPortInUse(port) {
  return new Promise((resolve) => {
    const server = createServer();
    server.once("error", (err) => {
      if (err.code === "EADDRINUSE") resolve(true);
      else resolve(false);
    });
    server.once("listening", () => {
      server.close(() => resolve(false));
    });
    server.listen(port, "0.0.0.0");
  });
}

function getRunningPid() {
  if (!existsSync(PID_FILE)) return null;
  const pid = parseInt(readFileSync(PID_FILE, "utf-8").trim(), 10);
  if (isNaN(pid)) return null;
  try {
    if (IS_WIN) {
      // process.kill(pid, 0) is unreliable on Windows — use tasklist
      const out = execSync(`tasklist /FI "PID eq ${pid}" /NH`, { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] });
      if (!out.includes(String(pid))) throw new Error("not found");
    } else {
      process.kill(pid, 0);
    }
    return pid;
  } catch {
    try { unlinkSync(PID_FILE); } catch { /* ignore */ }
    return null;
  }
}

function waitForServer(timeoutMs = 60000) {
  return new Promise((resolve, reject) => {
    const start = Date.now();
    const check = () => {
      if (Date.now() - start > timeoutMs) {
        return reject(new Error("Server startup timed out"));
      }
      import("http").then(({ default: http }) => {
        const req = http.get(`http://localhost:${PORT}`, (res) => {
          res.resume();
          resolve();
        });
        req.on("error", () => setTimeout(check, 500));
        req.setTimeout(2000, () => { req.destroy(); setTimeout(check, 500); });
      });
    };
    check();
  });
}

function npmCommand() {
  return IS_WIN ? "npm.cmd" : "npm";
}

// ─── Commands ───────────────────────────────────────────────────────────

async function buildIfNeeded() {
  const standaloneServer = resolve(PROJECT_ROOT, ".next/standalone/server.js");
  if (existsSync(standaloneServer)) return;

  log("First run — building production bundle...");
  log(`${DIM}This will take a minute, please wait...${RESET}`);

  try {
    execSync(`${npmCommand()} run build`, {
      cwd: PROJECT_ROOT,
      stdio: "inherit",
      env: { ...process.env, NODE_ENV: "production" },
      shell: true,
    });
    success("Build complete!");

    // Next.js standalone requires static assets + public to be copied manually
    const standaloneDir = resolve(PROJECT_ROOT, ".next/standalone");
    const staticSrc = resolve(PROJECT_ROOT, ".next/static");
    const staticDest = resolve(standaloneDir, ".next/static");
    const publicSrc = resolve(PROJECT_ROOT, "public");
    const publicDest = resolve(standaloneDir, "public");

    if (existsSync(staticSrc) && !existsSync(staticDest)) {
      log("Copying static assets...");
      cpSync(staticSrc, staticDest, { recursive: true });
    }
    if (existsSync(publicSrc) && !existsSync(publicDest)) {
      log("Copying public files...");
      cpSync(publicSrc, publicDest, { recursive: true });
    }
    success("Assets ready!");
  } catch (e) {
    error("Build failed. Check the output above.");
    process.exit(1);
  }
}

async function startServer() {
  const pid = getRunningPid();
  if (pid) {
    success(`aRouter is already running ${DIM}(PID: ${pid})${RESET}`);
    console.log();
    console.log(`  ${BOLD}Dashboard${RESET}  ${CYAN}${DASHBOARD_URL}${RESET}`);
    console.log();
    return;
  }

  const portBusy = await isPortInUse(PORT);
  if (portBusy) {
    error(`Port ${PORT} is already in use by another process.`);
    log(`Stop the other process or change the port.`);
    process.exit(1);
  }

  await buildIfNeeded();

  log("Starting aRouter...");

  let logFd;
  try {
    logFd = openSync(LOG_FILE, "a");
  } catch (e) {
    if (e.code === "EPERM") {
      // macOS SIP com.apple.provenance can lock the log file — use a fallback
      const fallbackLog = LOG_FILE.replace(".log", `-${Date.now()}.log`);
      warn(`Cannot write to ${LOG_FILE} (macOS SIP). Using ${fallbackLog}`);
      logFd = openSync(fallbackLog, "a");
    } else {
      throw e;
    }
  }

  const serverScript = resolve(PROJECT_ROOT, ".next/standalone/server.js");
  const child = spawn(process.execPath, [serverScript], {
    cwd: PROJECT_ROOT,  // Important: CWD must be PROJECT_ROOT so MITM can find src/mitm/
    detached: true,     // Must be true on ALL platforms so child survives parent exit
    stdio: ["ignore", logFd, logFd],
    env: {
      ...process.env,
      NODE_ENV: "production",
      PORT: String(PORT),
      HOSTNAME: "0.0.0.0",
      // In global install, src/ is not shipped — use the standalone copy
      MITM_SERVER_PATH: (() => {
        const standalone = resolve(PROJECT_ROOT, ".next/standalone/src/mitm/server.js");
        const source = resolve(PROJECT_ROOT, "src/mitm/server.js");
        return existsSync(standalone) ? standalone : source;
      })(),
    },
    ...(IS_WIN ? { windowsHide: true, shell: false } : {}),
  });

  writeFileSync(PID_FILE, String(child.pid));
  child.unref();

  try {
    process.stdout.write(`  Waiting for server `);
    await waitForServer(60000);
    process.stdout.write(`${GREEN}ready!${RESET}\n`);
  } catch {
    process.stdout.write(`${YELLOW}(timeout — check logs)${RESET}\n`);
  }

  console.log();
  console.log(`  ${GREEN}${BOLD}aRouter is running!${RESET}`);
  console.log();
  console.log(`  ${BOLD}Dashboard${RESET}  ${CYAN}${DASHBOARD_URL}${RESET}`);
  console.log(`  ${BOLD}API${RESET}        ${CYAN}${DASHBOARD_URL}/v1${RESET}`);
  console.log(`  ${BOLD}PID${RESET}        ${DIM}${child.pid}${RESET}`);
  console.log(`  ${BOLD}Logs${RESET}       ${DIM}${LOG_FILE}${RESET}`);
  console.log();
  console.log(`  ${DIM}Run ${RESET}arouter stop${DIM} to stop the server.${RESET}`);
  console.log();
}

function stopServer() {
  const pid = getRunningPid();
  if (!pid) {
    warn("aRouter is not running.");
    return;
  }

  log(`Stopping aRouter (PID: ${pid})...`);
  try {
    if (IS_WIN) {
      execSync(`taskkill /PID ${pid} /T /F`, { stdio: "ignore", shell: true });
    } else {
      process.kill(pid, "SIGTERM");
      setTimeout(() => {
        try {
          process.kill(pid, 0);
          process.kill(pid, "SIGKILL");
        } catch { /* already dead */ }
      }, 3000);
    }
  } catch (e) {
    warn(`Could not kill process: ${e.message}`);
  }

  try { unlinkSync(PID_FILE); } catch { /* ignore */ }
  success("aRouter stopped.");
}

function showStatus() {
  const pid = getRunningPid();
  if (pid) {
    success(`aRouter is running ${DIM}(PID: ${pid})${RESET}`);
    console.log(`  ${BOLD}Dashboard${RESET}  ${CYAN}${DASHBOARD_URL}${RESET}`);
  } else {
    warn("aRouter is not running.");
  }
}

function showLogs() {
  if (!existsSync(LOG_FILE)) {
    warn("No log file found. Start the server first.");
    return;
  }
  try {
    const content = readFileSync(LOG_FILE, "utf-8");
    const lines = content.split("\n").slice(-50);
    console.log(lines.join("\n"));
  } catch (e) {
    error(`Could not read logs: ${e.message}`);
  }
}

async function restartServer() {
  stopServer();
  await new Promise(r => setTimeout(r, 1500));
  await startServer();
}

function showHelp() {
  console.log();
  console.log(`  ${BOLD}aRouter${RESET} — AI Model Router & MITM Proxy`);
  console.log();
  console.log(`  ${BOLD}Usage:${RESET}`);
  console.log(`    ${CYAN}arouter${RESET}              Start the server (background)`);
  console.log(`    ${CYAN}arouter start${RESET}         Same as above`);
  console.log(`    ${CYAN}arouter stop${RESET}          Stop the server`);
  console.log(`    ${CYAN}arouter restart${RESET}       Restart the server`);
  console.log(`    ${CYAN}arouter status${RESET}        Show server status`);
  console.log(`    ${CYAN}arouter logs${RESET}          Show recent logs`);
  console.log(`    ${CYAN}arouter dev${RESET}           Run in foreground (dev mode)`);
  console.log(`    ${CYAN}arouter build${RESET}         Force rebuild`);
  console.log(`    ${CYAN}arouter help${RESET}          Show this help`);
  console.log();
  console.log(`  ${BOLD}Data:${RESET}  ${DIM}${DATA_DIR}${RESET}`);
  console.log(`  ${BOLD}Port:${RESET}  ${DIM}${PORT}${RESET}`);
  console.log();
}

function devMode() {
  log("Starting in dev mode (foreground)...");
  const child = spawn(npmCommand(), ["run", "dev"], {
    cwd: PROJECT_ROOT,
    stdio: "inherit",
    env: { ...process.env },
    shell: IS_WIN,
  });
  child.on("exit", (code) => process.exit(code || 0));
}

async function forceBuild() {
  log("Force rebuilding...");
  const nextDir = resolve(PROJECT_ROOT, ".next");
  try {
    if (IS_WIN) {
      execSync(`rmdir /s /q "${nextDir}"`, { cwd: PROJECT_ROOT, shell: true, stdio: "ignore" });
    } else {
      execSync(`rm -rf "${nextDir}"`, { cwd: PROJECT_ROOT, stdio: "ignore" });
    }
  } catch { /* ignore */ }
  await buildIfNeeded();
}

// ─── Main ───────────────────────────────────────────────────────────────

const command = process.argv[2] || "start";

switch (command) {
  case "start":   await startServer(); break;
  case "stop":    stopServer(); break;
  case "restart": await restartServer(); break;
  case "status":  showStatus(); break;
  case "logs":    showLogs(); break;
  case "dev":     devMode(); break;
  case "build":   await forceBuild(); break;
  case "help":
  case "--help":
  case "-h":      showHelp(); break;
  default:
    error(`Unknown command: ${command}`);
    showHelp();
    process.exit(1);
}
