import { promises as fs } from 'fs';
import TOML from '@iarna/toml';

// ---------------------------------------------------------------------------
// TOML formatting helpers — produce clean output with inline tables for env
// ---------------------------------------------------------------------------

function formatTomlValue(value) {
  if (typeof value === 'string') return JSON.stringify(value);
  if (typeof value === 'number' || typeof value === 'boolean') return String(value);
  if (Array.isArray(value)) return '[' + value.map(formatTomlValue).join(', ') + ']';
  if (value !== null && typeof value === 'object') {
    const pairs = Object.entries(value).map(([k, v]) => `${k} = ${formatTomlValue(v)}`).join(', ');
    return `{ ${pairs} }`;
  }
  return JSON.stringify(value);
}

// Fields that should always be written as inline tables even when they are objects
const INLINE_TABLE_FIELDS = new Set(['env', 'http_headers', 'env_http_headers']);

// Canonical field order for a mcp_servers entry
const FIELD_ORDER = [
  'command', 'url', 'args', 'env', 'env_vars', 'cwd',
  'bearer_token_env_var', 'http_headers', 'env_http_headers',
  'startup_timeout_sec', 'tool_timeout_sec', 'enabled', 'required',
];

function formatEntry(name, entry) {
  const lines = [`[mcp_servers.${name}]`];
  for (const key of FIELD_ORDER) {
    if (entry[key] === undefined) continue;
    const val = entry[key];
    if (INLINE_TABLE_FIELDS.has(key) && val !== null && typeof val === 'object' && !Array.isArray(val)) {
      const pairs = Object.entries(val).map(([k, v]) => `${k} = ${formatTomlValue(v)}`).join(', ');
      lines.push(`${key} = { ${pairs} }`);
    } else {
      lines.push(`${key} = ${formatTomlValue(val)}`);
    }
  }
  return lines.join('\n');
}

/**
 * Format a mcp_servers map as a clean TOML block.
 * Exported so that user-directories.js can append it to the base config template.
 */
export function formatMcpServersSection(mcpServers) {
  return Object.entries(mcpServers)
    .map(([name, entry]) => formatEntry(name, entry))
    .join('\n\n');
}

// ---------------------------------------------------------------------------
// Remove [mcp_servers.*] blocks from existing TOML text (text-based, safe)
// ---------------------------------------------------------------------------

function removeMcpServersFromContent(content) {
  const lines = content.split('\n');
  const result = [];
  let inMcpServers = false;

  for (const line of lines) {
    const trimmed = line.trim();
    if (/^\[mcp_servers[.\]]/.test(trimmed) || trimmed === '[mcp_servers]') {
      inMcpServers = true;
    } else if (inMcpServers && /^\[(?!mcp_servers)/.test(trimmed)) {
      inMcpServers = false;
    }
    if (!inMcpServers) result.push(line);
  }

  return result.join('\n');
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

/**
 * Convert a Claude mcp.json server config entry to the equivalent Codex
 * mcp_servers entry (drops the `type` discriminator, keeps compatible fields).
 */
export function mcpConfigToCodexEntry(serverConfig) {
  const { type, command, args, env, cwd, url, ...rest } = serverConfig;
  const entry = {};

  if (type === 'stdio' || (!type && command)) {
    if (command) entry.command = command;
    if (args?.length) entry.args = args;
    if (env && Object.keys(env).length > 0) entry.env = env;
    if (cwd) entry.cwd = cwd;
  } else if (type === 'http' || type === 'sse') {
    if (url) entry.url = url;
  }

  for (const key of ['startup_timeout_sec', 'tool_timeout_sec', 'enabled', 'required', 'bearer_token_env_var']) {
    if (rest[key] !== undefined) entry[key] = rest[key];
  }

  return entry;
}

/**
 * Read the mcp_servers map from a Codex config.toml.
 * Returns an empty object if the file doesn't exist or has no mcp_servers.
 */
export async function readCodexMcpServers(configPath) {
  try {
    const parsed = TOML.parse(await fs.readFile(configPath, 'utf-8'));
    return parsed.mcp_servers || {};
  } catch {
    return {};
  }
}

async function writeMcpServers(configPath, mcpServers) {
  let existingContent = '';
  try { existingContent = await fs.readFile(configPath, 'utf-8'); } catch { /* file missing */ }

  let newContent = removeMcpServersFromContent(existingContent).trimEnd();
  if (Object.keys(mcpServers).length > 0) {
    newContent += '\n\n' + formatMcpServersSection(mcpServers);
  }
  await fs.writeFile(configPath, newContent + '\n', 'utf-8');
}

/**
 * Add / update MCP servers in a Codex config.toml.
 * mcpJson: the parsed contents of a mcp.json file ({ [serverName]: config }).
 */
export async function addToCodexConfig(configPath, mcpJson) {
  const mcpServers = await readCodexMcpServers(configPath);
  for (const [name, serverConfig] of Object.entries(mcpJson)) {
    mcpServers[name] = mcpConfigToCodexEntry(serverConfig);
  }
  await writeMcpServers(configPath, mcpServers);
}

/**
 * Remove a single MCP server from a Codex config.toml.
 */
export async function removeFromCodexConfig(configPath, serverName) {
  const mcpServers = await readCodexMcpServers(configPath);
  if (!(serverName in mcpServers)) return;
  delete mcpServers[serverName];
  await writeMcpServers(configPath, mcpServers);
}
