import fs from "fs"
import path from "path"

import type { MCPTool } from "./mcp-service"
import { acpService } from "./acp-service"
import { agentProfileService } from "./agent-profile-service"
import { runtimeToolDefinitions } from "./runtime-tool-definitions"
import {
  getRuntimeFilesystemEnv,
  getRuntimeFilesystemPaths,
  type RuntimeFilesystemPaths,
} from "./runtime-filesystem-paths"

const RUNTIME_FILESYSTEM_VERSION = 1

interface RuntimeManifestTool {
  name: string
  description?: string
  sourceKind: "runtime" | "mcp"
  serverName?: string
  schemaFile: string
  schemaPath: string
}

function sanitizeToolSchemaFilename(toolName: string): string {
  return toolName
    .trim()
    .replace(/[^a-zA-Z0-9._-]+/g, "_")
    .replace(/^_+|_+$/g, "")
    || "tool"
}

function writeJsonFile(filePath: string, value: unknown): void {
  fs.mkdirSync(path.dirname(filePath), { recursive: true })
  fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8")
}

function writeTextFile(filePath: string, value: string): void {
  fs.mkdirSync(path.dirname(filePath), { recursive: true })
  fs.writeFileSync(filePath, value, "utf8")
}

function getSchemaFileName(toolName: string, seenBaseNames: Map<string, number>): string {
  const baseName = sanitizeToolSchemaFilename(toolName)
  const seenCount = seenBaseNames.get(baseName) ?? 0
  seenBaseNames.set(baseName, seenCount + 1)
  return seenCount === 0
    ? `${baseName}.schema.json`
    : `${baseName}_${seenCount + 1}.schema.json`
}

function normalizeToolsForManifest(availableTools?: MCPTool[]): MCPTool[] {
  const sourceTools = availableTools && availableTools.length > 0
    ? availableTools
    : runtimeToolDefinitions

  const uniqueTools = new Map<string, MCPTool>()
  for (const tool of sourceTools) {
    if (!tool?.name || uniqueTools.has(tool.name)) continue
    uniqueTools.set(tool.name, tool)
  }

  return Array.from(uniqueTools.values()).sort((a, b) => a.name.localeCompare(b.name))
}

function buildToolManifest(availableTools: MCPTool[] | undefined, paths: RuntimeFilesystemPaths): RuntimeManifestTool[] {
  const seenBaseNames = new Map<string, number>()

  return normalizeToolsForManifest(availableTools).map((tool) => {
    const schemaFile = getSchemaFileName(tool.name, seenBaseNames)
    const schemaPath = path.join(paths.toolSchemaDir, schemaFile)
    const serverSeparatorIndex = tool.name.indexOf(":")
    const sourceKind = serverSeparatorIndex >= 0 ? "mcp" : "runtime"

    writeJsonFile(schemaPath, {
      version: RUNTIME_FILESYSTEM_VERSION,
      generatedAt: new Date().toISOString(),
      toolName: tool.name,
      description: tool.description,
      inputSchema: tool.inputSchema ?? { type: "object", properties: {}, required: [] },
    })

    return {
      name: tool.name,
      description: tool.description,
      sourceKind,
      ...(sourceKind === "mcp" ? { serverName: tool.name.slice(0, serverSeparatorIndex) } : {}),
      schemaFile,
      schemaPath,
    }
  })
}

function buildAgentRegistry(): unknown[] {
  const byName = new Map<string, Record<string, unknown>>()

  try {
    for (const profile of agentProfileService.getAgentTargets()) {
      byName.set(profile.name, {
        source: "profile",
        id: profile.id,
        name: profile.name,
        displayName: profile.displayName,
        description: profile.description,
        enabled: profile.enabled,
        role: profile.role ?? (profile.isAgentTarget ? "delegation-target" : undefined),
        connectionType: profile.connection?.type,
        isDelegationTarget: true,
      })
    }
  } catch {
    // Best-effort runtime metadata should not block prompt construction.
  }

  try {
    for (const agent of acpService.getAgents()) {
      const name = agent.config?.name
      if (!name) continue

      const existing = byName.get(name)
      byName.set(name, {
        ...(existing ?? {
          source: "legacy-acp",
          name,
          displayName: agent.config.displayName,
          description: agent.config.description,
          enabled: agent.config.enabled,
          connectionType: agent.config.connection?.type,
          isDelegationTarget: true,
        }),
        acpStatus: agent.status,
      })
    }
  } catch {
    // Keep profile-based registry if ACP status is not available.
  }

  return Array.from(byName.values()).sort((a, b) =>
    String(a.name ?? "").localeCompare(String(b.name ?? "")),
  )
}

function buildRuntimeReadme(paths: RuntimeFilesystemPaths): string {
  const env = getRuntimeFilesystemEnv()
  return [
    "# DotAgents Runtime Filesystem",
    "",
    "This directory is generated by DotAgents for lightweight runtime discovery.",
    "Use direct runtime tools for host-side mutations such as setting the session title, delegation, and completion.",
    "",
    "Useful files:",
    `- agents.json: ${paths.agentRegistryPath}`,
    `- tools/index.json: ${paths.toolManifestPath}`,
    `- tools/schemas/: ${paths.toolSchemaDir}`,
    "",
    "Useful environment variables available to execute_command:",
    ...Object.keys(env).map((key) => `- ${key}`),
    "",
  ].join("\n")
}

export function ensureRuntimeFilesystemContext(
  availableTools?: MCPTool[],
  sessionId?: string,
): RuntimeFilesystemPaths {
  const paths = getRuntimeFilesystemPaths(sessionId)
  fs.mkdirSync(paths.toolSchemaDir, { recursive: true })
  if (paths.sessionDir) {
    fs.mkdirSync(paths.sessionDir, { recursive: true })
  }

  writeTextFile(path.join(paths.runtimeDir, "README.md"), buildRuntimeReadme(paths))

  writeJsonFile(paths.agentRegistryPath, {
    version: RUNTIME_FILESYSTEM_VERSION,
    generatedAt: new Date().toISOString(),
    agents: buildAgentRegistry(),
  })

  const tools = buildToolManifest(availableTools, paths)
  writeJsonFile(paths.toolManifestPath, {
    version: RUNTIME_FILESYSTEM_VERSION,
    generatedAt: new Date().toISOString(),
    tools,
  })

  return paths
}

export function formatRuntimeFilesystemContextForPrompt(paths: RuntimeFilesystemPaths): string {
  return [
    `Runtime metadata: ${paths.runtimeDir}`,
    `Agent registry: ${paths.agentRegistryPath}`,
    `Tool manifest: ${paths.toolManifestPath}`,
    `Tool schemas: ${paths.toolSchemaDir}`,
    ...(paths.sessionDir ? [`Session runtime dir: ${paths.sessionDir}`] : []),
  ].join("\n")
}
