import { NextRequest, NextResponse } from 'next/server'
import { createHash } from 'node:crypto'
import { requireRole } from '@/lib/auth'
import { logAuditEvent } from '@/lib/db'
import { config } from '@/lib/config'
import { validateBody, gatewayConfigUpdateSchema } from '@/lib/validation'
import { mutationLimiter } from '@/lib/rate-limit'
import { getDetectedGatewayToken } from '@/lib/gateway-runtime'

function getConfigPath(): string | null {
  return config.openclawConfigPath || null
}

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

function gatewayHeaders(): Record<string, string> {
  const token = getDetectedGatewayToken()
  const headers: Record<string, string> = { 'Content-Type': 'application/json' }
  if (token) headers['Authorization'] = `Bearer ${token}`
  return headers
}

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

/**
 * GET /api/gateway-config - Read the gateway configuration
 * GET /api/gateway-config?action=schema - Get the config JSON schema
 */
export async function GET(request: NextRequest) {
  const auth = requireRole(request, 'admin')
  if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })

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

  if (action === 'schema') {
    return getSchema()
  }

  const configPath = getConfigPath()
  if (!configPath) {
    return NextResponse.json({ error: 'OPENCLAW_CONFIG_PATH not configured' }, { status: 404 })
  }

  try {
    const { readFile } = require('fs/promises')
    const raw = await readFile(configPath, 'utf-8')
    const parsed = JSON.parse(raw)
    const hash = computeHash(raw)

    // Redact sensitive fields for display
    const redacted = redactSensitive(JSON.parse(JSON.stringify(parsed)))

    return NextResponse.json({
      path: configPath,
      config: redacted,
      raw_size: raw.length,
      hash,
    })
  } catch (err: any) {
    if (err.code === 'ENOENT') {
      return NextResponse.json({ error: 'Config file not found', path: configPath }, { status: 404 })
    }
    return NextResponse.json({ error: `Failed to read config: ${err.message}` }, { status: 500 })
  }
}

async function getSchema(): Promise<NextResponse> {
  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), 5000)
  try {
    const res = await fetch(gatewayUrl('/api/config/schema'), {
      signal: controller.signal,
      headers: gatewayHeaders(),
    })
    clearTimeout(timeout)
    if (!res.ok) {
      return NextResponse.json(
        { error: `Gateway returned ${res.status}` },
        { status: 502 },
      )
    }
    const data = await res.json()
    return NextResponse.json(data)
  } catch (err: any) {
    clearTimeout(timeout)
    return NextResponse.json(
      { error: err.name === 'AbortError' ? 'Gateway timeout' : 'Gateway unreachable' },
      { status: 502 },
    )
  }
}

/**
 * PUT /api/gateway-config - Update specific config fields
 * PUT /api/gateway-config?action=apply - Hot-apply config via gateway RPC
 * PUT /api/gateway-config?action=update - System update via gateway RPC
 *
 * Body: { updates: { "path.to.key": value, ... }, hash?: string }
 */
export async function PUT(request: NextRequest) {
  const auth = requireRole(request, 'admin')
  if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })

  const rateCheck = mutationLimiter(request)
  if (rateCheck) return rateCheck

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

  if (action === 'apply') {
    return applyConfig(request, auth)
  }

  if (action === 'update') {
    return updateSystem(request, auth)
  }

  const configPath = getConfigPath()
  if (!configPath) {
    return NextResponse.json({ error: 'OPENCLAW_CONFIG_PATH not configured' }, { status: 404 })
  }

  const result = await validateBody(request, gatewayConfigUpdateSchema)
  if ('error' in result) return result.error
  const body = result.data

  // Block writes to sensitive paths
  const blockedPaths = ['gateway.auth.password', 'gateway.auth.secret']
  for (const key of Object.keys(body.updates)) {
    if (blockedPaths.some(bp => key.startsWith(bp))) {
      return NextResponse.json({ error: `Cannot modify protected field: ${key}` }, { status: 403 })
    }
  }

  try {
    const { readFile, writeFile } = require('fs/promises')
    const raw = await readFile(configPath, 'utf-8')

    // Hash-based concurrency check
    const clientHash = (body as any).hash
    if (clientHash) {
      const serverHash = computeHash(raw)
      if (clientHash !== serverHash) {
        return NextResponse.json(
          { error: 'Config has been modified by another user. Please reload and try again.', code: 'CONFLICT' },
          { status: 409 },
        )
      }
    }

    const parsed = JSON.parse(raw)

    for (const dotPath of Object.keys(body.updates)) {
      const [rootKey] = dotPath.split('.')
      if (!rootKey || !(rootKey in parsed)) {
        return NextResponse.json(
          { error: `Unknown config root: ${rootKey || dotPath}` },
          { status: 400 },
        )
      }
    }

    // Apply updates via dot-notation
    const appliedKeys: string[] = []
    for (const [dotPath, value] of Object.entries(body.updates)) {
      setNestedValue(parsed, dotPath, value)
      appliedKeys.push(dotPath)
    }

    // Write back with pretty formatting
    const newRaw = JSON.stringify(parsed, null, 2) + '\n'
    await writeFile(configPath, newRaw)

    const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'
    logAuditEvent({
      action: 'gateway_config_update',
      actor: auth.user.username,
      actor_id: auth.user.id,
      detail: { updated_keys: appliedKeys },
      ip_address: ipAddress,
    })

    return NextResponse.json({
      updated: appliedKeys,
      count: appliedKeys.length,
      hash: computeHash(newRaw),
    })
  } catch (err: any) {
    return NextResponse.json({ error: `Failed to update config: ${err.message}` }, { status: 500 })
  }
}

async function applyConfig(request: NextRequest, auth: any): Promise<NextResponse> {
  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), 10000)
  try {
    const res = await fetch(gatewayUrl('/api/config/apply'), {
      method: 'POST',
      signal: controller.signal,
      headers: gatewayHeaders(),
    })
    clearTimeout(timeout)

    const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'
    logAuditEvent({
      action: 'gateway_config_apply',
      actor: auth.user.username,
      actor_id: auth.user.id,
      detail: { status: res.status },
      ip_address: ipAddress,
    })

    if (!res.ok) {
      const text = await res.text().catch(() => '')
      return NextResponse.json(
        { error: `Apply failed (${res.status}): ${text}` },
        { status: 502 },
      )
    }
    const data = await res.json().catch(() => ({}))
    return NextResponse.json({ ok: true, ...data })
  } catch (err: any) {
    clearTimeout(timeout)
    return NextResponse.json(
      { error: err.name === 'AbortError' ? 'Gateway timeout' : 'Gateway unreachable' },
      { status: 502 },
    )
  }
}

async function updateSystem(request: NextRequest, auth: any): Promise<NextResponse> {
  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), 15000)
  try {
    const res = await fetch(gatewayUrl('/api/config/update'), {
      method: 'POST',
      signal: controller.signal,
      headers: gatewayHeaders(),
    })
    clearTimeout(timeout)

    const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'
    logAuditEvent({
      action: 'gateway_config_system_update',
      actor: auth.user.username,
      actor_id: auth.user.id,
      detail: { status: res.status },
      ip_address: ipAddress,
    })

    if (!res.ok) {
      const text = await res.text().catch(() => '')
      return NextResponse.json(
        { error: `Update failed (${res.status}): ${text}` },
        { status: 502 },
      )
    }
    const data = await res.json().catch(() => ({}))
    return NextResponse.json({ ok: true, ...data })
  } catch (err: any) {
    clearTimeout(timeout)
    return NextResponse.json(
      { error: err.name === 'AbortError' ? 'Gateway timeout' : 'Gateway unreachable' },
      { status: 502 },
    )
  }
}

/** Set a value in a nested object using dot-notation path */
function setNestedValue(obj: any, path: string, value: any) {
  const keys = path.split('.')
  let current = obj
  for (let i = 0; i < keys.length - 1; i++) {
    if (current[keys[i]] === undefined) current[keys[i]] = {}
    current = current[keys[i]]
  }
  current[keys[keys.length - 1]] = value
}

/** Redact sensitive values for display */
function redactSensitive(obj: any, parentKey = ''): any {
  if (typeof obj !== 'object' || obj === null) return obj

  const sensitiveKeys = ['password', 'secret', 'token', 'api_key', 'apiKey']

  for (const key of Object.keys(obj)) {
    if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) {
      if (typeof obj[key] === 'string' && obj[key].length > 0) {
        obj[key] = '--------'
      }
    } else if (typeof obj[key] === 'object' && obj[key] !== null) {
      redactSensitive(obj[key], key)
    }
  }

  return obj
}
