import { readFile, writeFile, copyFile, access, mkdir } from "node:fs/promises";
import { join, basename } from "node:path";
import type { SkillsRegistry, SkillDefinition, McpServerDefinition } from "./types";

const SKILLS_DIR =
  process.env.SKILLS_DIR || "/opt/teletraan/skills";
const AGENTS_DIR =
  process.env.AGENTS_DIR || "/opt/teletraan/agents";
const REGISTRY_PATH = join(SKILLS_DIR, "registry.json");

// ─── Async Mutex for Write Safety ────────────────────────────────────
let writeQueue: Promise<void> = Promise.resolve();

function serialized<T>(fn: () => Promise<T>): Promise<T> {
  const result = writeQueue.then(fn);
  writeQueue = result.then(
    () => {},
    () => {}
  );
  return result;
}

// ─── Read / Write ────────────────────────────────────────────────────

export async function readRegistry(): Promise<SkillsRegistry> {
  try {
    const raw = await readFile(REGISTRY_PATH, "utf-8");
    return JSON.parse(raw) as SkillsRegistry;
  } catch {
    return { skills: {}, mcpServers: {} };
  }
}

async function writeRegistry(registry: SkillsRegistry): Promise<void> {
  await writeFile(REGISTRY_PATH, JSON.stringify(registry, null, 2));
}

// ─── Skill File Helpers ──────────────────────────────────────────────

function parseSkillFrontmatter(content: string): {
  name: string;
  description: string;
  version: string;
  allowedTools: string[];
  body: string;
} | null {
  const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
  if (!match) return null;

  const frontmatter = match[1];
  const body = match[2];

  const name = frontmatter.match(/^name:\s*(.+)$/m)?.[1]?.trim() || "";
  const description =
    frontmatter.match(/^description:\s*(.+)$/m)?.[1]?.trim() || "";
  const version = frontmatter.match(/^version:\s*(.+)$/m)?.[1]?.trim() || "";
  const toolsRaw =
    frontmatter.match(/^allowed-tools:\s*\[(.+)\]$/m)?.[1] || "";
  const allowedTools = toolsRaw
    .split(",")
    .map((t) => t.trim())
    .filter(Boolean);

  if (!name || !description) return null;

  return { name, description, version, allowedTools, body };
}

export async function readSkillFile(
  relativePath: string
): Promise<string | null> {
  try {
    return await readFile(join(SKILLS_DIR, relativePath), "utf-8");
  } catch {
    return null;
  }
}

export async function writeSkillFile(
  relativePath: string,
  content: string
): Promise<void> {
  await writeFile(join(SKILLS_DIR, relativePath), content);
}

// ─── Skills CRUD ─────────────────────────────────────────────────────

export async function listSkills(): Promise<SkillDefinition[]> {
  const registry = await readRegistry();
  const skills = Object.values(registry.skills);

  // Check file existence for each skill
  for (const skill of skills) {
    try {
      await access(join(SKILLS_DIR, skill.file));
      skill.fileNotFound = false;
    } catch {
      skill.fileNotFound = true;
    }
  }

  return skills;
}

export async function getSkill(
  name: string
): Promise<(SkillDefinition & { content?: string }) | null> {
  const registry = await readRegistry();
  const skill = registry.skills[name];
  if (!skill) return null;

  const content = await readSkillFile(skill.file);
  if (content === null) {
    return { ...skill, fileNotFound: true };
  }
  return { ...skill, fileNotFound: false, content };
}

export function addSkill(
  fileContent: string,
  fileName: string
): Promise<SkillDefinition> {
  return serialized(async () => {
    const parsed = parseSkillFrontmatter(fileContent);
    if (!parsed) throw new Error("Invalid skill file: missing or malformed YAML frontmatter");

    const registry = await readRegistry();
    if (registry.skills[parsed.name]) {
      throw new Error(`Skill "${parsed.name}" already exists`);
    }

    const broughtDir = join(SKILLS_DIR, "brought");
    await mkdir(broughtDir, { recursive: true });

    const safeName = `${parsed.name}.md`;
    const destPath = `brought/${safeName}`;
    await writeFile(join(broughtDir, safeName), fileContent);

    const skill: SkillDefinition = {
      name: parsed.name,
      description: parsed.description,
      version: parsed.version || "0.0.0",
      file: destPath,
      allowedTools: parsed.allowedTools,
      agents: [],
    };

    registry.skills[parsed.name] = skill;
    await writeRegistry(registry);
    return skill;
  });
}

export function addSkillFromPath(absolutePath: string): Promise<SkillDefinition> {
  return serialized(async () => {
    const fileContent = await readFile(absolutePath, "utf-8");
    const parsed = parseSkillFrontmatter(fileContent);
    if (!parsed) throw new Error("Invalid skill file: missing or malformed YAML frontmatter");

    const registry = await readRegistry();
    if (registry.skills[parsed.name]) {
      throw new Error(`Skill "${parsed.name}" already exists`);
    }

    const broughtDir = join(SKILLS_DIR, "brought");
    await mkdir(broughtDir, { recursive: true });

    const safeName = `${parsed.name}.md`;
    const destPath = `brought/${safeName}`;
    await copyFile(absolutePath, join(broughtDir, safeName));

    const skill: SkillDefinition = {
      name: parsed.name,
      description: parsed.description,
      version: parsed.version || "0.0.0",
      file: destPath,
      allowedTools: parsed.allowedTools,
      agents: [],
    };

    registry.skills[parsed.name] = skill;
    await writeRegistry(registry);
    return skill;
  });
}

export function updateSkill(
  name: string,
  updates: { description?: string; version?: string; allowedTools?: string[]; content?: string }
): Promise<SkillDefinition> {
  return serialized(async () => {
    const registry = await readRegistry();
    const skill = registry.skills[name];
    if (!skill) throw new Error(`Skill "${name}" not found`);

    if (updates.content !== undefined) {
      await writeFile(join(SKILLS_DIR, skill.file), updates.content);
    }

    // Explicit field values from the GUI take precedence over frontmatter
    if (updates.description !== undefined) skill.description = updates.description;
    if (updates.version !== undefined) skill.version = updates.version;
    if (updates.allowedTools !== undefined) skill.allowedTools = updates.allowedTools;

    registry.skills[name] = skill;
    await writeRegistry(registry);
    return skill;
  });
}

export function deleteSkill(
  name: string,
  deleteFile: boolean
): Promise<void> {
  return serialized(async () => {
    const registry = await readRegistry();
    const skill = registry.skills[name];
    if (!skill) throw new Error(`Skill "${name}" not found`);

    if (deleteFile) {
      try {
        const { unlink } = await import("node:fs/promises");
        await unlink(join(SKILLS_DIR, skill.file));
      } catch {
        // File may already be gone
      }
    }

    delete registry.skills[name];
    await writeRegistry(registry);
  });
}

export function updateSkillAgents(
  name: string,
  agents: string[]
): Promise<{ skill: SkillDefinition; previousAgents: string[] }> {
  return serialized(async () => {
    const registry = await readRegistry();
    const skill = registry.skills[name];
    if (!skill) throw new Error(`Skill "${name}" not found`);

    const previousAgents = [...skill.agents];
    skill.agents = agents;
    registry.skills[name] = skill;
    await writeRegistry(registry);
    return { skill, previousAgents };
  });
}

// ─── MCP Servers CRUD ────────────────────────────────────────────────

export async function listMcpServers(): Promise<McpServerDefinition[]> {
  const registry = await readRegistry();
  return Object.values(registry.mcpServers);
}

export async function getMcpServer(
  name: string
): Promise<McpServerDefinition | null> {
  const registry = await readRegistry();
  return registry.mcpServers[name] || null;
}

export function addMcpServer(
  server: McpServerDefinition
): Promise<McpServerDefinition> {
  return serialized(async () => {
    const registry = await readRegistry();
    if (registry.mcpServers[server.name]) {
      throw new Error(`MCP server "${server.name}" already exists`);
    }

    registry.mcpServers[server.name] = server;
    await writeRegistry(registry);
    return server;
  });
}

export function updateMcpServer(
  name: string,
  updates: { description?: string; command?: string; args?: string[]; env?: Record<string, string> }
): Promise<McpServerDefinition> {
  return serialized(async () => {
    const registry = await readRegistry();
    const server = registry.mcpServers[name];
    if (!server) throw new Error(`MCP server "${name}" not found`);

    if (updates.description !== undefined) server.description = updates.description;
    if (updates.command !== undefined) server.command = updates.command;
    if (updates.args !== undefined) server.args = updates.args;
    if (updates.env !== undefined) server.env = updates.env;

    registry.mcpServers[name] = server;
    await writeRegistry(registry);

    // Sync .mcp.json for all agents assigned to this server
    for (const agent of server.agents) {
      await syncAgentMcpJson(agent, registry);
    }

    return server;
  });
}

export function deleteMcpServer(name: string): Promise<string[]> {
  return serialized(async () => {
    const registry = await readRegistry();
    const server = registry.mcpServers[name];
    if (!server) throw new Error(`MCP server "${name}" not found`);

    const affectedAgents = [...server.agents];
    delete registry.mcpServers[name];
    await writeRegistry(registry);

    // Sync .mcp.json for affected agents
    for (const agent of affectedAgents) {
      await syncAgentMcpJson(agent, registry);
    }

    return affectedAgents;
  });
}

export function updateMcpServerAgents(
  name: string,
  agents: string[]
): Promise<{ server: McpServerDefinition; syncResults: { agent: string; ok: boolean; error?: string }[] }> {
  return serialized(async () => {
    const registry = await readRegistry();
    const server = registry.mcpServers[name];
    if (!server) throw new Error(`MCP server "${name}" not found`);

    const previousAgents = [...server.agents];
    server.agents = agents;
    registry.mcpServers[name] = server;
    await writeRegistry(registry);

    // Sync .mcp.json for all affected agents (added + removed)
    const allAffected = new Set([...previousAgents, ...agents]);
    const syncResults: { agent: string; ok: boolean; error?: string }[] = [];

    for (const agent of allAffected) {
      try {
        await syncAgentMcpJson(agent, registry);
        syncResults.push({ agent, ok: true });
      } catch (err) {
        syncResults.push({
          agent,
          ok: false,
          error: err instanceof Error ? err.message : String(err),
        });
      }
    }

    return { server, syncResults };
  });
}

// ─── MCP .mcp.json Sync ─────────────────────────────────────────────

async function syncAgentMcpJson(
  agentName: string,
  registry: SkillsRegistry
): Promise<void> {
  const mcpJsonPath = join(AGENTS_DIR, agentName, ".mcp.json");

  // Step 1: Read existing .mcp.json
  let existing: { mcpServers: Record<string, unknown> } = { mcpServers: {} };
  try {
    const raw = await readFile(mcpJsonPath, "utf-8");
    existing = JSON.parse(raw);
  } catch {
    // File doesn't exist or is invalid — start fresh
  }

  // Step 2: Extract the channel server entry (keyed by agent name)
  const channelEntry = existing.mcpServers?.[agentName];

  // Step 3: Build new MCP servers from registry
  const newServers: Record<string, unknown> = {};

  // Preserve channel entry first
  if (channelEntry) {
    newServers[agentName] = channelEntry;
  }

  // Add registry-managed servers assigned to this agent
  for (const [serverName, serverDef] of Object.entries(registry.mcpServers)) {
    if (serverDef.agents.includes(agentName)) {
      newServers[serverName] = {
        command: serverDef.command,
        args: serverDef.args,
        env: serverDef.env,
      };
    }
  }

  // Step 4: Write merged result
  await writeFile(
    mcpJsonPath,
    JSON.stringify({ mcpServers: newServers }, null, 2)
  );
}

// ─── Channel Server Reload ──────────────────────────────────────────

export async function reloadAgentSkills(agentName: string): Promise<boolean> {
  // Read agents.json to get the agent's port and secret
  try {
    const agentsJson = await readFile(join(AGENTS_DIR, "agents.json"), "utf-8");
    const agents = JSON.parse(agentsJson) as {
      agents: Record<string, { port: number; enabled?: boolean }>;
    };
    const agent = agents.agents[agentName];
    if (!agent || agent.enabled === false) return false;

    // Read the agent's .mcp.json to get the channel secret
    const mcpRaw = await readFile(join(AGENTS_DIR, agentName, ".mcp.json"), "utf-8");
    const mcpJson = JSON.parse(mcpRaw) as {
      mcpServers: Record<string, { env?: Record<string, string> }>;
    };
    const channelEntry = mcpJson.mcpServers[agentName];
    const secret = channelEntry?.env?.ONE_CHANNEL_SECRET;
    if (!secret) return false;

    const resp = await fetch(`http://127.0.0.1:${agent.port}/reload-skills`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${secret}`,
        "Content-Type": "application/json",
      },
    });
    return resp.ok;
  } catch {
    return false;
  }
}
