import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { execFile } from "node:child_process";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { promisify } from "node:util";

import { FULL_CATALOG_IDENTITY } from "../../dist/catalogIdentity.js";
import { TOOL_CONTRACTS } from "../../dist/contracts/toolContracts.js";
import {
  requireMcpLifecycleResult,
  sortVerifierNames,
} from "./mcp-verifier-results.mjs";

const execute = promisify(execFile);

/** Install the selected archive or registry specifier before any protocol deadline. */
export async function installCanaryPackage(specifier, workspace, environment) {
  const prefix = join(workspace, "installed");
  await execute(
    "npm",
    [
      "install",
      "--ignore-scripts",
      "--no-save",
      "--no-package-lock",
      "--no-audit",
      "--no-fund",
      "--prefix",
      prefix,
      specifier,
    ],
    { cwd: workspace, env: environment, maxBuffer: 16 * 1024 * 1024 },
  );
  return join(prefix, "node_modules", "rea-agents");
}

/** Exercise the same installed CLI/MCP contract before and after publication. */
export async function verifyInstalledCanaryPackage(
  packageRoot,
  version,
  workspace,
  environment,
) {
  const installed = JSON.parse(
    await readFile(join(packageRoot, "package.json"), "utf8"),
  );
  if (installed.name !== "rea-agents" || installed.version !== version)
    throw new Error(
      `Installed package identity is ${installed.name}@${installed.version}, expected rea-agents@${version}`,
    );
  const launcher = join(packageRoot, "scripts", "rea.mjs");
  for (const option of ["--help", "--llms"])
    await execute(process.execPath, [launcher, option], {
      cwd: workspace,
      env: environment,
      maxBuffer: 16 * 1024 * 1024,
    });
  const transport = new StdioClientTransport({
    command: process.execPath,
    args: [launcher, "mcp"],
    cwd: workspace,
    env: environment,
    stderr: "pipe",
  });
  const client = new Client({ name: "release-package-canary", version: "1" });
  let stderr = "";
  transport.stderr?.on("data", (chunk) => {
    if (stderr.length < 16_384)
      stderr += chunk.toString("utf8").slice(0, 16_384 - stderr.length);
  });
  try {
    await client.connect(transport);
    const tools = await client.listTools();
    const toolNames = tools.tools.map(({ name }) => name);
    const canonicalToolNames = new Set(TOOL_CONTRACTS.map(({ name }) => name));
    const publishedToolNames = new Set(toolNames);
    const unknown = toolNames.filter((name) => !canonicalToolNames.has(name));
    const missing = [...canonicalToolNames].filter(
      (name) => !publishedToolNames.has(name),
    );
    if (
      toolNames.length === 0 ||
      publishedToolNames.size !== toolNames.length ||
      unknown.length ||
      missing.length
    )
      throw new Error(
        `Installed MCP exposed an invalid canonical tool catalog (${toolNames.length} tools, unknown: ${unknown.join(", ") || "none"}, missing: ${missing.join(", ") || "none"})`,
      );
    const status = requireMcpLifecycleResult(
      await client.callTool({ name: "binary_session", arguments: {} }),
      "binary_session",
    );
    const availability = sortVerifierNames(
      status.tool_availability.map(({ name }) => name),
    );
    if (
      JSON.stringify(availability) !==
      JSON.stringify(sortVerifierNames(canonicalToolNames))
    )
      throw new Error(
        "Installed binary_session omitted canonical tool availability",
      );
    return {
      mcpTools: toolNames.length,
      canonicalMcpTools: FULL_CATALOG_IDENTITY.counts.mcp_tools,
    };
  } catch (cause) {
    throw new Error(`Installed package MCP verification failed: ${stderr}`, {
      cause,
    });
  } finally {
    try {
      await client.close();
    } finally {
      await transport.close();
    }
  }
}
