import { spawn } from "node:child_process";
import { fileExists } from "./config.js";
import { sanitizePublicData } from "./public-sanitizer.js";

export async function codexHealth(config, dependencies = {}) {
  const exists = dependencies.fileExists ?? fileExists;
  const execute = dependencies.runCommand ?? runCommand;
  const probeServer = dependencies.probeAppServer ?? probeAppServer;
  const cliExists = await exists(config.codexCliPath);
  const version = cliExists
    ? await execute(config.codexCliPath, ["--version"], {}, 5000)
    : { ok: false, stdout: "", stderr: "codex cli missing" };

  const appServerHelp = cliExists
    ? await execute(config.codexCliPath, ["app-server", "--help"], {}, 5000)
    : { ok: false, stdout: "", stderr: "codex cli missing" };

  const accounts = await Promise.all(config.accounts.map(async (account) => {
    const historyOnly = Boolean(account.historyOnly);
    return {
      id: account.id,
      label: account.label,
      historyOnly,
      home: await exists(account.codexHome) ? "present" : "missing",
      authentication: historyOnly
        ? "not_applicable"
        : await exists(`${account.codexHome}\\auth.json`) ? "present" : "missing"
    };
  }));

  const executionAccounts = config.accounts.filter((account) => !account.historyOnly);
  const probeAccount = executionAccounts.find((account) => account.id === "account-a") ?? executionAccounts[0];
  const appServerProbe = cliExists && probeAccount
    ? await probeServer(config.codexCliPath, probeAccount.codexHome)
    : { ok: false, error: "no probe account" };

  return sanitizePublicData({
    engine: config.engine,
    cli: {
      pathConfigured: true,
      exists: cliExists,
      version: trimOutput(version.stdout),
      appServerHelpOk: appServerHelp.ok
    },
    accounts,
    appServerProbe
  }, config);
}

function runCommand(command, args, env, timeoutMs) {
  return new Promise((resolve) => {
    const child = spawn(command, args, {
      env: { ...process.env, ...env },
      shell: needsShell(command),
      windowsHide: true
    });
    let stdout = "";
    let stderr = "";
    const timer = setTimeout(() => {
      child.kill();
      resolve({ ok: false, stdout, stderr: `${stderr}\ntimeout`.trim() });
    }, timeoutMs);

    child.stdout.on("data", (chunk) => {
      stdout += chunk.toString("utf8");
    });
    child.stderr.on("data", (chunk) => {
      stderr += chunk.toString("utf8");
    });
    child.on("error", (error) => {
      clearTimeout(timer);
      resolve({ ok: false, stdout, stderr: error.message });
    });
    child.on("close", (code) => {
      clearTimeout(timer);
      resolve({ ok: code === 0, stdout, stderr });
    });
  });
}

function probeAppServer(command, codexHome) {
  return new Promise((resolve) => {
    const child = spawn(command, ["app-server", "--stdio"], {
      env: { ...process.env, CODEX_HOME: codexHome },
      stdio: ["pipe", "pipe", "pipe"],
      shell: needsShell(command),
      windowsHide: true
    });

    let buffer = "";
    let stderr = "";
    let resolved = false;
    const timer = setTimeout(() => {
      finish({ ok: false, error: "timeout", stderr: trimOutput(stderr) });
    }, 8000);

    child.stdout.on("data", (chunk) => {
      buffer += chunk.toString("utf8");
      const lines = buffer.split(/\r?\n/);
      buffer = lines.pop() ?? "";
      for (const line of lines) {
        if (!line.trim()) continue;
        try {
          const message = JSON.parse(line);
          if (message.id === 1) {
            finish({
              ok: !message.error,
              error: message.error?.message,
              resultKeys: message.result ? Object.keys(message.result) : []
            });
          }
        } catch (error) {
          finish({ ok: false, error: `invalid json: ${error.message}` });
        }
      }
    });

    child.stderr.on("data", (chunk) => {
      stderr += chunk.toString("utf8");
    });
    child.on("error", (error) => {
      finish({ ok: false, error: error.message });
    });
    child.on("close", (code) => {
      if (!resolved) {
        finish({ ok: false, error: `closed with ${code}`, stderr: trimOutput(stderr) });
      }
    });

    child.stdin.write(JSON.stringify({
      id: 1,
      method: "initialize",
      params: {
        clientInfo: {
          name: "codex_remote",
          title: "Code Remote Manager",
          version: "0.1.0"
        }
      }
    }) + "\n");
    child.stdin.write(JSON.stringify({ method: "initialized", params: {} }) + "\n");

    function finish(result) {
      if (resolved) return;
      resolved = true;
      clearTimeout(timer);
      child.kill();
      resolve(result);
    }
  });
}

function trimOutput(value) {
  return value.trim().split(/\r?\n/).slice(-3).join("\n");
}

function needsShell(command) {
  return command.toLowerCase().endsWith(".cmd") || command.toLowerCase().endsWith(".bat");
}
