import { jsonResponse, errorResponse } from '../utils/cors'
import { existsSync, readdirSync, readFileSync, statSync, mkdirSync, writeFileSync } from 'fs'
import { join, resolve, basename } from 'path'
import { homedir } from 'os'

const PROJECT_ROOT = resolve(import.meta.dir, '../..')

// Allowed config file patterns (for path validation on read/write)
const ALLOWED_PATTERNS = [
  /^\.claude\/.*$/,
  /^\.claude-[^/]+\/.*$/,
  /^CLAUDE\.md$/,
  /^\.mcp\.json$/
]

// Skip .git internals (hundreds of binary/pack files, useless for debugging)
const SKIP_DIRS = ['.git']

// Sensitive files blocked from read/write via API
const BLOCKED_FILES = ['.credentials.json']

// Base tool names known to Claude Code
const BASE_TOOLS = [
  'Read', 'Edit', 'Write', 'Bash', 'Grep', 'Glob',
  'WebFetch', 'WebSearch', 'Task', 'NotebookEdit',
  'Skill', 'EnterPlanMode', 'ExitPlanMode', 'AskUserQuestion',
  'TodoRead', 'TodoWrite'
]

// Hook event types
type HookEventType = 'UserPromptSubmit' | 'PreToolUse' | 'PostToolUse' | 'SessionStart' | 'Stop' | 'Notification' | 'PermissionRequest'

const HOOK_EVENT_TYPES: HookEventType[] = [
  'UserPromptSubmit', 'PreToolUse', 'PostToolUse',
  'SessionStart', 'Stop', 'Notification', 'PermissionRequest'
]

type FileCategory = 'config' | 'instructions' | 'plugins' | 'history' | 'debug' | 'cache' | 'sessions' | 'backups' | 'other'

interface AgentFile {
  name: string
  path: string
  type: 'json' | 'markdown' | 'text' | 'jsonl'
  category: FileCategory
  size: number
}

interface UiConfig {
  label: string
  shortLabel: string
  color: string
  gradient: string
  terminalBg: string
  terminalBorder: string
  enabled: boolean
}

interface Agent {
  id: string
  name: string
  directory: string
  files: AgentFile[]
  uiConfig: UiConfig | null
}

interface ParsedPermission {
  raw: string
  tool: string
  params: string | null
  category: 'base' | 'mcp'
  server?: string
  host?: string
}

// ── Permission string parser ──

function parsePermission(raw: string): ParsedPermission {
  // MCP tools: mcp__<server>__<host>-<toolName>
  const mcpMatch = raw.match(/^mcp__([^_]+(?:_[^_]+)?)__([^-]+)-(.+)$/)
  if (mcpMatch) {
    return {
      raw,
      tool: mcpMatch[3],
      params: null,
      category: 'mcp',
      server: mcpMatch[1].replace(/_/g, '-'),
      host: mcpMatch[2]
    }
  }

  // Parameterized: Tool(params)
  const paramMatch = raw.match(/^(\w+)\((.+)\)$/)
  if (paramMatch) {
    return {
      raw,
      tool: paramMatch[1],
      params: paramMatch[2],
      category: BASE_TOOLS.includes(paramMatch[1]) ? 'base' : 'mcp'
    }
  }

  // Simple: ToolName
  return {
    raw,
    tool: raw,
    params: null,
    category: BASE_TOOLS.includes(raw) ? 'base' : 'mcp'
  }
}

// ── File utilities ──

function getFileType(filename: string): AgentFile['type'] {
  if (filename.endsWith('.jsonl')) return 'jsonl'
  if (filename.endsWith('.json')) return 'json'
  if (filename.endsWith('.md')) return 'markdown'
  return 'text'
}

function categorizeFile(relPath: string, filename: string): FileCategory {
  if (filename.includes('.backup')) return 'backups'
  if (relPath.includes('/plugins/')) return 'plugins'
  if (relPath.includes('/debug/')) return 'debug'
  if (relPath.includes('/cache/')) return 'cache'
  if (relPath.includes('/session-env/') || relPath.includes('/shell-snapshots/') || relPath.includes('/todos/') || relPath.includes('/projects/')) return 'sessions'
  if (filename === 'history.jsonl') return 'history'
  if (filename.endsWith('.md')) return 'instructions'
  if (filename === 'settings.json' || filename === '.claude.json' || filename === '.mcp.json') return 'config'
  if (filename.endsWith('.json')) return 'config'
  return 'other'
}

function isBlocked(filename: string): boolean {
  return BLOCKED_FILES.includes(filename)
}

function isAllowedPath(relativePath: string): boolean {
  const normalized = relativePath.replace(/\\/g, '/')
  return ALLOWED_PATTERNS.some(p => p.test(normalized))
}

function scanDirectory(dir: string, relBase: string): AgentFile[] {
  const files: AgentFile[] = []
  if (!existsSync(dir)) return files

  try {
    const entries = readdirSync(dir)
    for (const entry of entries) {
      const fullPath = join(dir, entry)
      const relPath = `${relBase}/${entry}`

      let stat
      try { stat = statSync(fullPath) } catch { continue }

      if (isBlocked(entry)) continue

      if (stat.isFile()) {
        files.push({
          name: entry,
          path: relPath,
          type: getFileType(entry),
          category: categorizeFile(relPath, entry),
          size: stat.size
        })
      } else if (stat.isDirectory()) {
        if (SKIP_DIRS.includes(entry)) continue
        files.push(...scanDirectory(fullPath, relPath))
      }
    }
  } catch { /* permission errors */ }

  return files
}

function readUiConfig(agentDir: string): UiConfig | null {
  const uiPath = join(agentDir, 'ui.json')
  if (!existsSync(uiPath)) return null
  try {
    const raw = readFileSync(uiPath, 'utf-8')
    const data = JSON.parse(raw)
    return {
      label: data.label || '',
      shortLabel: data.shortLabel || '',
      color: data.color || '#6366f1',
      gradient: data.gradient || '',
      terminalBg: data.terminalBg || '#0f0a1a',
      terminalBorder: data.terminalBorder || '#6366f1',
      enabled: data.enabled !== false
    }
  } catch {
    return null
  }
}

function readJsonFile(path: string): any {
  try {
    if (!existsSync(path)) return null
    return JSON.parse(readFileSync(path, 'utf-8'))
  } catch {
    return null
  }
}

function writeJsonFile(path: string, data: any): void {
  const dir = resolve(path, '..')
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
  writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf-8')
}

// ── Config file targeting ──

function getSettingsPath(agentId: string): string {
  if (agentId === 'main') {
    return join(PROJECT_ROOT, '.claude', 'settings.local.json')
  }
  return join(PROJECT_ROOT, `.claude-${agentId}`, 'settings.json')
}

function getSkillsDir(agentId: string): string {
  if (agentId === 'main') {
    return join(PROJECT_ROOT, '.claude', 'skills')
  }
  return join(PROJECT_ROOT, `.claude-${agentId}`, 'skills')
}

function getMcpJsonPath(): string {
  return join(PROJECT_ROOT, '.mcp.json')
}

// ── Agent discovery ──

function discoverAgents(): Agent[] {
  const agents: Agent[] = []

  const claudeDir = join(PROJECT_ROOT, '.claude')
  if (existsSync(claudeDir)) {
    const mainAgent: Agent = {
      id: 'main',
      name: 'Claude Code (main)',
      directory: '.claude',
      files: scanDirectory(claudeDir, '.claude'),
      uiConfig: readUiConfig(claudeDir)
    }

    const claudeMd = join(PROJECT_ROOT, 'CLAUDE.md')
    if (existsSync(claudeMd)) {
      const stat = statSync(claudeMd)
      mainAgent.files.unshift({
        name: 'CLAUDE.md', path: 'CLAUDE.md', type: 'markdown',
        category: 'instructions', size: stat.size
      })
    }

    const mcpJson = join(PROJECT_ROOT, '.mcp.json')
    if (existsSync(mcpJson)) {
      const stat = statSync(mcpJson)
      mainAgent.files.push({
        name: '.mcp.json', path: '.mcp.json', type: 'json',
        category: 'config', size: stat.size
      })
    }

    agents.push(mainAgent)
  }

  try {
    const rootEntries = readdirSync(PROJECT_ROOT)
    for (const entry of rootEntries) {
      if (!entry.startsWith('.claude-')) continue
      const fullPath = join(PROJECT_ROOT, entry)
      try { if (!statSync(fullPath).isDirectory()) continue } catch { continue }

      const agentId = entry.replace('.claude-', '')
      agents.push({
        id: agentId,
        name: agentId.charAt(0).toUpperCase() + agentId.slice(1),
        directory: entry,
        files: scanDirectory(fullPath, entry),
        uiConfig: readUiConfig(fullPath)
      })
    }
  } catch { /* ignore */ }

  return agents
}

// ── Skill discovery ──

function discoverSkills(agentId: string): any[] {
  const skills: any[] = []
  const skillsDir = getSkillsDir(agentId)

  if (!existsSync(skillsDir)) {
    // Fallback for sub-agents: try main agent's skills dir
    if (agentId !== 'main') {
      const mainSkillsDir = getSkillsDir('main')
      if (existsSync(mainSkillsDir)) {
        return discoverSkillsFromDir(mainSkillsDir)
      }
    }
    return skills
  }

  return discoverSkillsFromDir(skillsDir)
}

function discoverSkillsFromDir(dir: string): any[] {
  const skills: any[] = []
  if (!existsSync(dir)) return skills

  try {
    const entries = readdirSync(dir)
    for (const entry of entries) {
      const entryPath = join(dir, entry)
      let stat
      try { stat = statSync(entryPath) } catch { continue }

      if (stat.isDirectory()) {
        const skillMdPath = join(entryPath, 'SKILL.md')
        if (existsSync(skillMdPath)) {
          const content = readFileSync(skillMdPath, 'utf-8')
          const nameMatch = content.match(/^#\s+(.+)/m)
          const descMatch = content.match(/^(?:#+\s+.+\n+)(.+)/m)

          // Find referenced files
          const references: { name: string; path: string }[] = []
          try {
            const skillEntries = readdirSync(entryPath)
            for (const se of skillEntries) {
              if (se !== 'SKILL.md') {
                references.push({ name: se, path: join(entryPath, se) })
              }
            }
          } catch { /* ignore */ }

          skills.push({
            name: nameMatch ? nameMatch[1].trim() : entry,
            description: descMatch ? descMatch[1].trim() : '',
            path: entryPath,
            skillMdContent: content,
            references
          })
        }
      }
    }
  } catch { /* ignore */ }

  return skills
}

// ── Plugin discovery ──

function discoverPlugins(): any[] {
  const plugins: any[] = []
  const pluginsDir = join(homedir(), '.claude', 'plugins', 'marketplaces')

  if (!existsSync(pluginsDir)) return plugins

  try {
    const entries = readdirSync(pluginsDir)
    for (const entry of entries) {
      const entryPath = join(pluginsDir, entry)
      let stat
      try { stat = statSync(entryPath) } catch { continue }

      if (stat.isDirectory()) {
        // Look for plugin manifest (package.json or plugin.json)
        const pkgPath = join(entryPath, 'package.json')
        const pluginJsonPath = join(entryPath, 'plugin.json')

        let manifest: any = readJsonFile(pkgPath) || readJsonFile(pluginJsonPath)
        if (manifest) {
          plugins.push({
            name: manifest.name || entry,
            description: manifest.description || '',
            author: manifest.author || '',
            mcpConfig: manifest.mcpConfig || manifest.mcp || null,
            installed: true
          })
        } else {
          plugins.push({
            name: entry,
            description: '',
            author: '',
            mcpConfig: null,
            installed: true
          })
        }
      }
    }
  } catch { /* ignore */ }

  return plugins
}

// ── Known tools discovery ──

function discoverKnownTools(): { baseTools: string[]; mcpTools: Record<string, Record<string, string[]>> } {
  const mcpTools: Record<string, Record<string, string[]>> = {}
  const agents = discoverAgents()

  for (const agent of agents) {
    const settings = readJsonFile(getSettingsPath(agent.id))
    if (!settings?.permissions) continue

    const allPerms = [
      ...(settings.permissions.allow || []),
      ...(settings.permissions.deny || [])
    ]

    for (const raw of allPerms) {
      const parsed = parsePermission(raw)
      if (parsed.category === 'mcp' && parsed.server && parsed.host) {
        if (!mcpTools[parsed.server]) mcpTools[parsed.server] = {}
        if (!mcpTools[parsed.server][parsed.host]) mcpTools[parsed.server][parsed.host] = []
        if (!mcpTools[parsed.server][parsed.host].includes(parsed.tool)) {
          mcpTools[parsed.server][parsed.host].push(parsed.tool)
        }
      }
    }
  }

  return { baseTools: BASE_TOOLS, mcpTools }
}

// ── Handlers ──

export async function handleAgents(req: Request): Promise<Response | null> {
  if (req.method !== 'GET') return null
  return jsonResponse(discoverAgents())
}

export async function handleAgentsFile(req: Request, url: URL): Promise<Response | null> {
  const filePath = url.searchParams.get('path')
  if (!filePath) return errorResponse('Missing "path" query parameter')

  const normalized = filePath.replace(/\\/g, '/')
  if (!isAllowedPath(normalized)) return errorResponse('Access denied: path not allowed', 403)
  if (isBlocked(basename(normalized))) return errorResponse('Access denied: sensitive file', 403)

  const absolutePath = resolve(PROJECT_ROOT, normalized)
  if (!absolutePath.startsWith(PROJECT_ROOT)) return errorResponse('Access denied: path traversal', 403)

  if (req.method === 'GET') {
    if (!existsSync(absolutePath)) return errorResponse('File not found', 404)
    try {
      const content = await Bun.file(absolutePath).text()
      return jsonResponse({ path: normalized, content })
    } catch (e: any) {
      return errorResponse(`Failed to read file: ${e.message}`, 500)
    }
  }

  if (req.method === 'POST') {
    try {
      const body = await req.json()
      if (typeof body.content !== 'string') return errorResponse('Missing "content" field')

      if (normalized.endsWith('.json')) {
        try { JSON.parse(body.content) } catch { return errorResponse('Invalid JSON content') }
      }

      await Bun.write(absolutePath, body.content)
      return jsonResponse({ success: true, path: normalized })
    } catch (e: any) {
      return errorResponse(`Failed to write file: ${e.message}`, 500)
    }
  }

  return null
}

export async function handleAgentsConfig(req: Request, url: URL): Promise<Response | null> {
  if (req.method !== 'GET') return null

  const agentId = url.searchParams.get('agentId') || 'main'
  const settingsPath = getSettingsPath(agentId)
  const settings = readJsonFile(settingsPath) || {}

  const allowRaw: string[] = settings.permissions?.allow || []
  const denyRaw: string[] = settings.permissions?.deny || []

  return jsonResponse({
    agentId,
    configFile: settingsPath.replace(PROJECT_ROOT + '/', '').replace(PROJECT_ROOT + '\\', ''),
    permissions: {
      allow: allowRaw.map(parsePermission),
      deny: denyRaw.map(parsePermission)
    },
    hooks: settings.hooks || {},
    env: settings.env || {},
    enableAllProjectMcpServers: settings.enableAllProjectMcpServers ?? false,
    enabledMcpjsonServers: settings.enabledMcpjsonServers || []
  })
}

export async function handleAgentsKnownTools(req: Request): Promise<Response | null> {
  if (req.method !== 'GET') return null
  return jsonResponse(discoverKnownTools())
}

export async function handleAgentsSkills(req: Request, url: URL): Promise<Response | null> {
  if (req.method !== 'GET') return null
  const agentId = url.searchParams.get('agentId') || 'main'
  return jsonResponse(discoverSkills(agentId))
}

export async function handleAgentsPlugins(req: Request): Promise<Response | null> {
  if (req.method !== 'GET') return null
  return jsonResponse(discoverPlugins())
}

export async function handleAgentsMcpJson(req: Request): Promise<Response | null> {
  if (req.method !== 'GET') return null
  const mcpPath = getMcpJsonPath()
  const data = readJsonFile(mcpPath)
  return jsonResponse(data || { mcpServers: {} })
}

export async function handleAgentsConfigPermissions(req: Request): Promise<Response | null> {
  if (req.method !== 'POST') return null

  try {
    const body = await req.json()
    const agentId = body.agentId || 'main'
    const settingsPath = getSettingsPath(agentId)
    const settings = readJsonFile(settingsPath) || {}

    settings.permissions = {
      allow: body.permissions?.allow || [],
      deny: body.permissions?.deny || []
    }

    writeJsonFile(settingsPath, settings)
    return jsonResponse({ success: true })
  } catch (e: any) {
    return errorResponse(`Failed to save permissions: ${e.message}`, 500)
  }
}

export async function handleAgentsConfigHooks(req: Request): Promise<Response | null> {
  if (req.method !== 'POST') return null

  try {
    const body = await req.json()
    const agentId = body.agentId || 'main'
    const settingsPath = getSettingsPath(agentId)
    const settings = readJsonFile(settingsPath) || {}

    settings.hooks = body.hooks || {}

    writeJsonFile(settingsPath, settings)
    return jsonResponse({ success: true })
  } catch (e: any) {
    return errorResponse(`Failed to save hooks: ${e.message}`, 500)
  }
}

export async function handleAgentsConfigMcp(req: Request): Promise<Response | null> {
  if (req.method !== 'POST') return null

  try {
    const body = await req.json()
    const mcpPath = getMcpJsonPath()

    writeJsonFile(mcpPath, { mcpServers: body.mcpServers || {} })
    return jsonResponse({ success: true })
  } catch (e: any) {
    return errorResponse(`Failed to save MCP config: ${e.message}`, 500)
  }
}
