import { NextRequest, NextResponse } from 'next/server'
import { createHash } from 'node:crypto'
import { requireRole } from '@/lib/auth'
import { config } from '@/lib/config'
import { logger } from '@/lib/logger'
import path from 'node:path'

function gatewayUrl(p: string): string {
  return `http://${config.gatewayHost}:${config.gatewayPort}${p}`
}

function execApprovalsPath(): string {
  return path.join(config.openclawHome, 'exec-approvals.json')
}

function computeHash(raw: string): string {
  return createHash('sha256').update(raw, 'utf8').digest('hex')
}

/**
 * GET /api/exec-approvals - Fetch pending execution approval requests
 * GET /api/exec-approvals?action=allowlist - Fetch per-agent allowlists
 */
export async function GET(request: NextRequest) {
  const auth = requireRole(request, 'operator')
  if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })

  const action = request.nextUrl.searchParams.get('action')

  if (action === 'allowlist') {
    return getAllowlist()
  }

  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), 5000)

  try {
    const res = await fetch(gatewayUrl('/api/exec-approvals'), {
      signal: controller.signal,
      headers: { 'Accept': 'application/json' },
    })
    clearTimeout(timeout)

    if (!res.ok) {
      logger.warn({ status: res.status }, 'Gateway exec-approvals endpoint returned error')
      return NextResponse.json({ approvals: [] })
    }

    const data = await res.json()
    return NextResponse.json(data)
  } catch (err: any) {
    clearTimeout(timeout)
    if (err.name === 'AbortError') {
      logger.warn('Gateway exec-approvals request timed out')
    } else {
      logger.warn({ err }, 'Gateway exec-approvals unreachable')
    }
    return NextResponse.json({ approvals: [] })
  }
}

async function getAllowlist(): Promise<NextResponse> {
  const filePath = execApprovalsPath()
  try {
    const { readFile } = require('fs/promises')
    const raw = await readFile(filePath, 'utf-8')
    const parsed = JSON.parse(raw)
    const agents: Record<string, { pattern: string }[]> = {}
    if (parsed?.agents && typeof parsed.agents === 'object') {
      for (const [agentId, agentConfig] of Object.entries(parsed.agents)) {
        const cfg = agentConfig as any
        if (Array.isArray(cfg?.allowlist)) {
          agents[agentId] = cfg.allowlist.map((e: any) => ({ pattern: String(e?.pattern ?? '') }))
        } else {
          agents[agentId] = []
        }
      }
    }
    return NextResponse.json({ agents, hash: computeHash(raw) })
  } catch (err: any) {
    if (err.code === 'ENOENT') {
      return NextResponse.json({ agents: {}, hash: computeHash('') })
    }
    logger.warn({ err }, 'Failed to read exec-approvals config')
    return NextResponse.json({ error: `Failed to read config: ${err.message}` }, { status: 500 })
  }
}

/**
 * PUT /api/exec-approvals - Save allowlist changes
 * Body: { agents: Record<string, { pattern: string }[]>, hash?: string }
 */
export async function PUT(request: NextRequest) {
  const auth = requireRole(request, 'operator')
  if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })

  let body: { agents: Record<string, { pattern: string }[]>; hash?: string }
  try {
    body = await request.json()
  } catch {
    return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
  }

  if (!body.agents || typeof body.agents !== 'object') {
    return NextResponse.json({ error: 'Missing required field: agents' }, { status: 400 })
  }

  const filePath = execApprovalsPath()
  try {
    const { readFile, writeFile, mkdir } = require('fs/promises')
    const { existsSync } = require('fs')

    let parsed: any = { version: 1, agents: {} }
    try {
      const raw = await readFile(filePath, 'utf-8')
      parsed = JSON.parse(raw)

      if (body.hash) {
        const serverHash = computeHash(raw)
        if (body.hash !== serverHash) {
          return NextResponse.json(
            { error: 'Config has been modified. Please reload and try again.', code: 'CONFLICT' },
            { status: 409 },
          )
        }
      }
    } catch (err: any) {
      if (err.code !== 'ENOENT') throw err
    }

    if (!parsed.agents) parsed.agents = {}

    for (const [agentId, patterns] of Object.entries(body.agents)) {
      if (!parsed.agents[agentId]) parsed.agents[agentId] = {}
      if (patterns.length === 0) {
        delete parsed.agents[agentId].allowlist
      } else {
        parsed.agents[agentId].allowlist = patterns.map((p: { pattern: string }) => ({
          pattern: String(p.pattern ?? ''),
        }))
      }
    }

    const dir = path.dirname(filePath)
    if (!existsSync(dir)) {
      await mkdir(dir, { recursive: true })
    }

    const newRaw = JSON.stringify(parsed, null, 2) + '\n'
    await writeFile(filePath, newRaw, { mode: 0o600 })

    return NextResponse.json({ ok: true, hash: computeHash(newRaw) })
  } catch (err: any) {
    logger.error({ err }, 'Failed to save exec-approvals config')
    return NextResponse.json({ error: `Failed to save: ${err.message}` }, { status: 500 })
  }
}

/**
 * POST /api/exec-approvals - Respond to an execution approval request
 * Body: { id: string, action: 'approve' | 'deny' | 'always_allow', reason?: string }
 */
export async function POST(request: NextRequest) {
  const auth = requireRole(request, 'operator')
  if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })

  let body: { id: string; action: string; reason?: string }
  try {
    body = await request.json()
  } catch {
    return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
  }

  if (!body.id || typeof body.id !== 'string') {
    return NextResponse.json({ error: 'Missing required field: id' }, { status: 400 })
  }

  const validActions = ['approve', 'deny', 'always_allow']
  if (!validActions.includes(body.action)) {
    return NextResponse.json({ error: `Invalid action. Must be one of: ${validActions.join(', ')}` }, { status: 400 })
  }

  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), 5000)

  try {
    const res = await fetch(gatewayUrl('/api/exec-approvals/respond'), {
      method: 'POST',
      signal: controller.signal,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        id: body.id,
        action: body.action,
        reason: body.reason,
      }),
    })
    clearTimeout(timeout)

    const data = await res.json()
    return NextResponse.json(data, { status: res.status })
  } catch (err: any) {
    clearTimeout(timeout)
    if (err.name === 'AbortError') {
      logger.error('Gateway exec-approvals respond request timed out')
      return NextResponse.json({ error: 'Gateway request timed out' }, { status: 504 })
    }
    logger.error({ err }, 'Gateway exec-approvals respond failed')
    return NextResponse.json({ error: 'Gateway unreachable' }, { status: 502 })
  }
}
