#!/usr/bin/env node

import { spawn } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import readline from "node:readline";
import { fileURLToPath } from "node:url";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const child = spawn(process.execPath, [path.join(root, "bin", "carbon-mcp.mjs")], {
  cwd: root,
  stdio: ["pipe", "pipe", "pipe"],
});
const lines = readline.createInterface({ input: child.stdout });
const pending = new Map();
let requestId = 0;
let stderr = "";

child.stderr.on("data", (chunk) => { stderr += String(chunk); });
lines.on("line", (line) => {
  let message;
  try { message = JSON.parse(line); } catch { return; }
  const waiter = pending.get(message.id);
  if (!waiter) return;
  pending.delete(message.id);
  waiter.resolve(message);
});

function request(method, params) {
  const id = ++requestId;
  const message = { jsonrpc: "2.0", id, method };
  if (params !== undefined) message.params = params;
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      pending.delete(id);
      reject(new Error(`Timed out waiting for ${method}. ${stderr}`));
    }, 10_000);
    pending.set(id, {
      resolve: (value) => { clearTimeout(timer); resolve(value); },
    });
    child.stdin.write(`${JSON.stringify(message)}\n`);
  });
}

function assert(condition, message) {
  if (!condition) throw new Error(message);
}

function toolPayload(message) {
  const block = message.result?.content?.find((item) => item.type === "text");
  assert(block?.text, "Tool result did not include a JSON text payload.");
  return JSON.parse(block.text);
}

try {
  await request("initialize", {
    protocolVersion: "2025-06-18",
    capabilities: {},
    clientInfo: { name: "carbon-local-smoke", version: "1" },
  });
  const tools = (await request("tools/list")).result?.tools || [];
  const prompts = (await request("prompts/list")).result?.prompts || [];
  const cloudTools = tools.filter((tool) => tool.name.startsWith("testers_"));
  const cloudPrompts = prompts.filter((prompt) => prompt.name.startsWith("testers_"));
  assert(tools.length > 0, "No CARBON tools were discovered.");
  assert(prompts.length > 0, "No CARBON prompts were discovered.");
  assert(tools.some((tool) => tool.name === "carbon_human_context"), "CARBON human decision-context tool is missing.");
  assert(tools.some((tool) => tool.name === "carbon_feedback_context"), "CARBON feedback-context tool is missing.");
  assert(prompts.some((prompt) => prompt.name === "carbon_human"), "CARBON human workflow prompt is missing.");
  assert(prompts.some((prompt) => prompt.name === "carbon_feedback"), "CARBON feedback workflow prompt is missing.");
  assert(cloudTools.length === 0, `Cloud tools remain public: ${cloudTools.map((tool) => tool.name).join(", ")}`);
  assert(cloudPrompts.length === 0, `Cloud prompts remain public: ${cloudPrompts.map((prompt) => prompt.name).join(", ")}`);

  const blockedTool = await request("tools/call", {
    name: "testers_get_account_status",
    arguments: {},
  });
  assert(Boolean(blockedTool.error || blockedTool.result?.isError), "A hidden Cloud tool remained callable.");

  const blockedPrompt = await request("prompts/get", { name: "testers_run", arguments: {} });
  assert(Boolean(blockedPrompt.error), "A hidden Cloud prompt remained callable.");

  const project = fs.mkdtempSync(path.join(os.tmpdir(), "carbon-human-mcp-"));
  fs.writeFileSync(path.join(project, "package.json"), JSON.stringify({ name: "human-workspace-smoke" }));
  fs.mkdirSync(path.join(project, "src"));
  fs.writeFileSync(path.join(project, "src", "app.ts"), "export const release = 'production'; async function checkout(){ return payment(); }");
  try {
    const generated = toolPayload(await request("tools/call", {
      name: "carbon_human_context",
      arguments: { action: "generate", root: project, size: "quick", scope: "production checkout" },
    }));
    assert(generated.workspace?.url?.includes("/human#"), "Human workspace was not returned as a protected in-agent page.");
    assert(fs.existsSync(generated.report?.reportPath), "Human command did not generate a durable report snapshot.");
    const question = generated.context.questions.find((item) => item.key === "release-context");
    assert(question?.options?.some((item) => item.id === "production"), "Human quick-answer options were not generated.");
    const answered = toolPayload(await request("tools/call", {
      name: "carbon_human_context",
      arguments: { action: "answer", root: project, questionId: question.id, optionId: "production", comment: "Customer release next week." },
    }));
    assert(answered.question?.selectedOptionId === "production", "Human quick-answer selection was not persisted.");
    assert(fs.existsSync(answered.report?.reportPath), "Human answer did not refresh the durable report snapshot.");
    const feedback = toolPayload(await request("tools/call", {
      name: "carbon_feedback_context",
      arguments: { action: "create", root: project, title: "Checkout feedback", focus: "Does checkout feel credible for a first-time customer?", artifact: "https://example.test/checkout", artifactKind: "url" },
    }));
    assert(feedback.workspace?.url?.includes("/feedback#"), "Feedback workspace was not returned as a protected in-agent page.");
    assert(fs.existsSync(feedback.report?.reportPath), "Feedback command did not generate a durable report snapshot.");
    const recorded = toolPayload(await request("tools/call", {
      name: "carbon_feedback_context",
      arguments: { action: "respond", root: project, requestId: feedback.request.id, rating: 4, category: "usability", response: "The primary action is clear, but show the final total earlier." },
    }));
    assert(recorded.response?.rating === 4, "Local feedback was not persisted through MCP.");
  } finally {
    fs.rmSync(project, { recursive: true, force: true });
  }

  console.log(`PASS CARBON local-only MCP surface: ${tools.length} tools · ${prompts.length} prompts · Cloud entry points blocked`);
} finally {
  child.kill("SIGTERM");
  lines.close();
}
