import { readdir, readFile } from "node:fs/promises";
import { join } from "node:path";
import type { AgentConfig, AgentServerConfig, AgentDetails, McpServerInfo } from "./types";

const AGENTS_DIR =
  process.env.AGENTS_DIR || "/opt/teletraan/agents";

const VALID_NAME = /^[a-z0-9-]+$/;
const CACHE_TTL = 30_000;

let cachedAgents: AgentServerConfig[] | null = null;
let cacheTime = 0;

function parseClaudeMd(content: string): {
  displayName: string;
  description: string;
} {
  const lines = content.split("\n");
  let displayName = "";
  let description = "";

  for (const line of lines) {
    const heading = line.match(/^#\s+(.+)/);
    if (heading && !displayName) {
      displayName = heading[1].replace(/\s*—.*$/, "").trim();
      continue;
    }
    if (displayName && !description && line.trim()) {
      description = line.trim();
      break;
    }
  }

  return { displayName, description };
}

function parseMcpJson(content: string): { port: number; secret: string } | null {
  try {
    const config = JSON.parse(content) as {
      mcpServers?: Record<
        string,
        { env?: Record<string, string> }
      >;
    };

    if (!config.mcpServers) return null;

    const firstServer = Object.values(config.mcpServers)[0];
    const env = firstServer?.env;
    if (!env) return null;

    const portStr = env.ONE_CHANNEL_PORT;
    const secret = env.ONE_CHANNEL_SECRET;
    if (!portStr || !secret) return null;

    return { port: parseInt(portStr, 10), secret };
  } catch {
    return null;
  }
}

export async function discoverAgents(): Promise<AgentServerConfig[]> {
  if (cachedAgents && Date.now() - cacheTime < CACHE_TTL) {
    return cachedAgents;
  }

  const agents: AgentServerConfig[] = [];

  let entries: string[];
  try {
    const dirents = await readdir(AGENTS_DIR, { withFileTypes: true });
    entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
  } catch {
    return [];
  }

  for (const name of entries) {
    if (!VALID_NAME.test(name)) continue;

    const agentDir = join(AGENTS_DIR, name);

    let mcpRaw: string;
    try {
      mcpRaw = await readFile(join(agentDir, ".mcp.json"), "utf-8");
    } catch {
      continue; // Skip agents without .mcp.json
    }

    const mcpConfig = parseMcpJson(mcpRaw);
    if (!mcpConfig) continue;

    let displayName = name.charAt(0).toUpperCase() + name.slice(1);
    let description = "";

    try {
      const claudeMd = await readFile(join(agentDir, "CLAUDE.md"), "utf-8");
      const parsed = parseClaudeMd(claudeMd);
      if (parsed.displayName) displayName = parsed.displayName;
      if (parsed.description) description = parsed.description;
    } catch {
      // CLAUDE.md is optional
    }

    agents.push({
      name,
      displayName,
      description,
      port: mcpConfig.port,
      secret: mcpConfig.secret,
      channelUrl: `http://127.0.0.1:${mcpConfig.port}`,
    });
  }

  cachedAgents = agents;
  cacheTime = Date.now();
  return agents;
}

export async function getAgent(
  name: string
): Promise<AgentServerConfig | null> {
  if (!VALID_NAME.test(name)) return null;
  const agents = await discoverAgents();
  return agents.find((a) => a.name === name) ?? null;
}

export function toClientConfig(agent: AgentServerConfig): AgentConfig {
  return {
    name: agent.name,
    displayName: agent.displayName,
    description: agent.description,
    port: agent.port,
  };
}

interface AgentsJsonEntry {
  displayName?: string;
  description?: string;
  port?: number;
  fifo?: string;
  channelDir?: string;
  enabled?: boolean;
}

function parseMcpServers(content: string): McpServerInfo[] {
  try {
    const config = JSON.parse(content) as {
      mcpServers?: Record<
        string,
        { command?: string; args?: string[] }
      >;
    };
    if (!config.mcpServers) return [];
    return Object.entries(config.mcpServers).map(([name, srv]) => ({
      name,
      command: srv.command || "",
      args: srv.args || [],
    }));
  } catch {
    return [];
  }
}

export async function getAgentDetails(
  name: string
): Promise<AgentDetails | null> {
  const agent = await getAgent(name);
  if (!agent) return null;

  const agentDir = join(AGENTS_DIR, name);

  // Read agents.json for extra fields
  let fifo = "";
  let channelDir = "";
  let enabled = true;
  try {
    const raw = await readFile(join(AGENTS_DIR, "agents.json"), "utf-8");
    const registry = JSON.parse(raw) as { agents?: Record<string, AgentsJsonEntry> };
    const entry = registry.agents?.[name];
    if (entry) {
      fifo = entry.fifo || "";
      channelDir = entry.channelDir || "";
      enabled = entry.enabled !== false;
    }
  } catch {
    // agents.json is optional
  }

  // Read full CLAUDE.md content
  let claudeMd = "";
  try {
    claudeMd = await readFile(join(agentDir, "CLAUDE.md"), "utf-8");
  } catch {
    // CLAUDE.md is optional
  }

  // Read MCP servers (strip secrets from env)
  let mcpServers: McpServerInfo[] = [];
  try {
    const mcpRaw = await readFile(join(agentDir, ".mcp.json"), "utf-8");
    mcpServers = parseMcpServers(mcpRaw);
  } catch {
    // .mcp.json already validated in getAgent
  }

  return {
    name: agent.name,
    displayName: agent.displayName,
    description: agent.description,
    port: agent.port,
    claudeMd,
    mcpServers,
    enabled,
    fifo,
    channelDir,
  };
}
