import 'server-only'

import { randomUUID } from 'node:crypto'
import { appendFile, mkdir, rename, stat } from 'node:fs/promises'
import path from 'node:path'
import { logOperationEvent } from '@/lib/observability/operation-events'

const DEFAULT_FILE = 'logs/trip_ai_chat_copilot.jsonl'
const DEFAULT_MAX_BYTES = 20 * 1024 * 1024
const REQUEST_ID = /^[A-Za-z0-9._:-]{8,128}$/
const SENSITIVE_KEY = /authorization|cookie|password|secret|api[_-]?key|access[_-]?token|jwt|audio|content|question|answer/i

let writeQueue: Promise<void> = Promise.resolve()

export function tripAiRequestId(req?: Request): string {
  const supplied = req?.headers.get('x-trip-ai-request-id')?.trim() || ''
  return REQUEST_ID.test(supplied) ? supplied : randomUUID()
}

function safe(value: unknown, key = ''): unknown {
  if (SENSITIVE_KEY.test(key) && !key.toLowerCase().endsWith('_chars') && !key.toLowerCase().endsWith('_count')) {
    return '[REDACTED]'
  }
  if (value === null || ['boolean', 'number'].includes(typeof value)) return value
  if (typeof value === 'string') return value.slice(0, 500)
  if (Array.isArray(value)) return value.slice(0, 50).map((item) => safe(item))
  if (value && typeof value === 'object') {
    return Object.fromEntries(
      Object.entries(value as Record<string, unknown>).map(([childKey, child]) => [
        childKey,
        safe(child, childKey),
      ]),
    )
  }
  return String(value).slice(0, 500)
}

function logFile(): string {
  const configured = process.env.TRIP_AI_LOG_FILE?.trim() || DEFAULT_FILE
  return path.isAbsolute(configured) ? configured : path.join(process.cwd(), configured)
}

function maxBytes(): number {
  const configured = Number(process.env.TRIP_AI_LOG_MAX_BYTES)
  return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_MAX_BYTES
}

async function appendLine(line: string): Promise<void> {
  const file = logFile()
  await mkdir(path.dirname(file), { recursive: true })
  try {
    const current = await stat(file)
    if (current.size + Buffer.byteLength(line) > maxBytes()) {
      const stamp = new Date().toISOString().replace(/[:.]/g, '-')
      await rename(file, `${file}.${stamp}`)
    }
  } catch (error) {
    const code = (error as NodeJS.ErrnoException).code
    if (code !== 'ENOENT') throw error
  }
  await appendFile(file, line, { encoding: 'utf8', mode: 0o600 })
}

export async function logTripAiEvent(
  event: string,
  fields: Record<string, unknown> = {},
): Promise<void> {
  const record = {
    timestamp: new Date().toISOString(),
    service: 'workspace',
    component: 'trip_ai_chat_copilot',
    event,
    status: fields.status || 'ok',
    ...Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, safe(value, key)])),
  }
  const line = `${JSON.stringify(record)}\n`

  // Console output keeps the same events available on serverless hosts where
  // the local filesystem is ephemeral.
  console.info(line.trim())
  writeQueue = writeQueue.then(() => appendLine(line)).catch((error: unknown) => {
    console.error(
      JSON.stringify({
        timestamp: new Date().toISOString(),
        service: 'workspace',
        component: 'trip_ai_chat_copilot',
        event: 'log_write_failed',
        status: 'error',
        error_type: error instanceof Error ? error.name : 'UnknownError',
      }),
    )
  })
  await writeQueue

  // The JSONL file is useful for request-level debugging. Keep terminal
  // outcomes in the database as well so admins can search across workflows.
  if (/_completed$|_failed$|_denied$/.test(event) || event === 'gpt_not_configured') {
    await logOperationEvent({
      area: event.startsWith('trip_sync_') ? 'integration' : 'ai',
      event,
      outcome: /_failed$|_denied$|not_configured$/.test(event) ? 'failed' : 'success',
      tripId: typeof fields.trip_id === 'string' ? fields.trip_id : null,
      requestId: typeof fields.request_id === 'string' ? fields.request_id : null,
      detail: {
        status: typeof fields.status === 'string' ? fields.status : null,
        error_code: typeof fields.error_code === 'string' ? fields.error_code : null,
      },
    })
  }
}
