#!/usr/bin/env node
import { mkdir, readFile, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";

const FOUNDRY_SERVER_NAME = "nodedc_module_foundry";
const ONTOLOGY_SERVER_NAME = "nodedc_ontology";
const SKILL_NAME = "foundry-context";
const MCP_PROTOCOL_VERSION = "2025-06-18";

function usage(message = "") {
  if (message) console.error(message);
  console.log(`Usage:
  nodedc-foundry-codex-agent --server <Foundry URL> --code <one-time code>
  nodedc-foundry-codex-agent doctor [--codex-home <path>]

The setup command is issued by Foundry Settings → Codex Agent API.`);
  process.exitCode = message ? 1 : 0;
}

function parseArgs(raw) {
  const args = raw[0] === "--" ? raw.slice(1) : raw;
  const out = { command: "setup", server: "", code: "", codexHome: process.env.CODEX_HOME || "" };
  if (args[0] === "doctor") out.command = "doctor";
  const values = out.command === "doctor" ? args.slice(1) : args;
  for (let index = 0; index < values.length; index += 1) {
    const item = values[index];
    if (item === "--server") out.server = String(values[++index] || "");
    else if (item.startsWith("--server=")) out.server = item.slice("--server=".length);
    else if (item === "--code") out.code = String(values[++index] || "");
    else if (item.startsWith("--code=")) out.code = item.slice("--code=".length);
    else if (item === "--codex-home") out.codexHome = String(values[++index] || "");
    else if (item.startsWith("--codex-home=")) out.codexHome = item.slice("--codex-home=".length);
    else if (item === "--help" || item === "-h") out.help = true;
    else throw new Error(`unknown_argument:${item}`);
  }
  out.server = String(out.server || "").replace(/\/+$/, "");
  return out;
}

function codexHome(value) {
  if (!value) return path.join(os.homedir(), ".codex");
  if (value === "~") return os.homedir();
  if (value.startsWith("~/") || value.startsWith("~\\")) return path.join(os.homedir(), value.slice(2));
  return path.resolve(value);
}

async function readIfExists(file) {
  try {
    return await readFile(file, "utf8");
  } catch (error) {
    if (error?.code === "ENOENT") return null;
    throw error;
  }
}

function stripMcpBlocks(content, serverNames) {
  const targets = serverNames.map((name) => `mcp_servers.${name}`);
  let skip = false;
  const kept = [];
  for (const line of String(content || "").split(/\r?\n/)) {
    const section = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/)?.[1]?.trim() || "";
    if (section) skip = targets.some((target) => section === target || section.startsWith(`${target}.`));
    if (!skip) kept.push(line);
  }
  return kept.join("\n").trimEnd();
}

function mcpBlock(serverName, endpoint, token) {
  return [
    `[mcp_servers.${serverName}]`,
    `url = ${JSON.stringify(endpoint)}`,
    "enabled = true",
    "required = false",
    "startup_timeout_sec = 30",
    "tool_timeout_sec = 120",
    "",
    `[mcp_servers.${serverName}.http_headers]`,
    `Authorization = ${JSON.stringify(`Bearer ${token}`)}`,
    `Accept = ${JSON.stringify("application/json, text/event-stream")}`,
    `${JSON.stringify("MCP-Protocol-Version")} = ${JSON.stringify(MCP_PROTOCOL_VERSION)}`,
  ].join("\n");
}

async function writeConfig(home, setup) {
  const configPath = path.join(home, "config.toml");
  await mkdir(path.dirname(configPath), { recursive: true });
  const original = await readIfExists(configPath);
  if (original !== null) await writeFile(`${configPath}.nodedc-foundry-agent.bak`, original, "utf8");
  const prefix = stripMcpBlocks(original || "", [FOUNDRY_SERVER_NAME, ONTOLOGY_SERVER_NAME]);
  const blocks = [
    mcpBlock(FOUNDRY_SERVER_NAME, setup.foundry.mcpUrl, setup.foundry.token),
    mcpBlock(ONTOLOGY_SERVER_NAME, setup.ontology.mcpUrl, setup.ontology.token),
  ].join("\n\n");
  await writeFile(configPath, `${prefix}${prefix ? "\n\n" : ""}${blocks}\n`, "utf8");
  return configPath;
}

function skillBody() {
  return `---
name: foundry-context
description: Use for NODE.DC Module Foundry application work. Use Foundry and the separate read-only Ontology MCP; never depend on Foundry source files.
---

# NODE.DC Module Foundry

- Treat \`nodedc_module_foundry\` as the only authority for Foundry application instances and Page Library bindings. Do not read or edit Foundry source, runtime files or manifests directly.
- Treat \`nodedc_ontology\` as a separate read-only semantic authority. Use it before inventing entity ids, semantic types, aliases, relations or routing assumptions.
- Work only inside the currently granted Foundry user contour. Sharing, cross-user visibility, roles and public release are outside this connector until explicit tools expose them.
- Canonical Page Library templates are immutable. Create and modify application instances through Foundry tools only.
- Use a fresh idempotency key for every write. Reuse a key only when intentionally replaying the exact same operation.
- Provider URLs, credentials, polling and normalization stay in NDC Engine L2. Foundry consumes provider-neutral Data Products and semantic bindings only.
- Never request or place provider tokens, internal workload grants or raw secret values in an application, page binding, prompt or MCP argument.
- For the first Map slice, resolve semantics through Ontology, create the application/page in Foundry, bind the approved Data Product, and verify the resulting application manifest through Foundry MCP reads.
`;
}

async function writeSkill(home) {
  const file = path.join(home, "skills", SKILL_NAME, "SKILL.md");
  await mkdir(path.dirname(file), { recursive: true });
  await writeFile(file, skillBody(), "utf8");
  return file;
}

async function redeem(server, code) {
  const response = await fetch(`${server}/api/foundry-agent/v1/setup/redeem`, {
    method: "POST",
    headers: { accept: "application/json", "content-type": "application/json" },
    body: JSON.stringify({ code, deviceName: `${os.hostname()} Codex Desktop` }),
  });
  const payload = await response.json().catch(() => ({}));
  if (!response.ok || !payload?.ok || !payload?.foundry?.token || !payload?.ontology?.token) {
    throw new Error(payload?.error || `setup_redeem_failed_${response.status}`);
  }
  return payload;
}

async function toolsSmoke(serverName, endpoint, token) {
  const headers = {
    accept: "application/json, text/event-stream",
    authorization: `Bearer ${token}`,
    "content-type": "application/json",
    "mcp-protocol-version": MCP_PROTOCOL_VERSION,
  };
  const initialize = await fetch(endpoint, {
    method: "POST",
    headers,
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "initialize",
      params: { protocolVersion: MCP_PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: "nodedc-foundry-setup", version: "0.1.0" } },
    }),
  });
  if (!initialize.ok) throw new Error(`${serverName}_initialize_failed_${initialize.status}`);
  const list = await fetch(endpoint, {
    method: "POST",
    headers,
    body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }),
  });
  const payload = await list.json().catch(() => ({}));
  if (!list.ok || !Array.isArray(payload?.result?.tools)) throw new Error(`${serverName}_tools_list_failed_${list.status}`);
  return payload.result.tools.length;
}

async function main() {
  const args = parseArgs(process.argv.slice(2));
  if (args.help) return usage();
  const home = codexHome(args.codexHome);
  if (args.command === "doctor") {
    const config = await readIfExists(path.join(home, "config.toml"));
    const skill = await readIfExists(path.join(home, "skills", SKILL_NAME, "SKILL.md"));
    const ok = Boolean(
      config?.includes(`[mcp_servers.${FOUNDRY_SERVER_NAME}]`)
      && config?.includes(`[mcp_servers.${ONTOLOGY_SERVER_NAME}]`)
      && skill,
    );
    console.log(ok ? "NODE.DC Foundry + Ontology Codex install is present." : "NODE.DC Foundry + Ontology Codex install is incomplete.");
    process.exitCode = ok ? 0 : 1;
    return;
  }
  if (!args.server || !args.code) return usage("server_and_code_required");
  const setup = await redeem(args.server, args.code);
  const configPath = await writeConfig(home, setup);
  const skillPath = await writeSkill(home);
  const foundryToolCount = await toolsSmoke(FOUNDRY_SERVER_NAME, setup.foundry.mcpUrl, setup.foundry.token);
  const ontologyToolCount = await toolsSmoke(ONTOLOGY_SERVER_NAME, setup.ontology.mcpUrl, setup.ontology.token);
  console.log("NODE.DC Foundry + Ontology Codex setup complete.");
  console.log("Config:", configPath);
  console.log("Skill:", skillPath);
  console.log("Foundry MCP smoke tools:", foundryToolCount);
  console.log("Ontology MCP smoke tools:", ontologyToolCount);
  console.log("Restart Codex Desktop completely before using the new MCP tools.");
}

main().catch((error) => {
  console.error(`NODE.DC Foundry Codex setup failed: ${error?.message || error}`);
  process.exitCode = 1;
});
