/**
 * Audit logging (§35).
 *
 * Records who did what, to which entity, from where, and what changed. Writes are
 * deliberately best-effort: an audit failure must never take down the operation being
 * audited, but it must be visible in the server log rather than swallowed silently.
 */

import 'server-only'

import { prisma } from './db'
import type { CurrentUser } from './auth/current-user'

export const AUDIT_ACTIONS = {
  loginSucceeded: 'auth.login',
  loginFailed: 'auth.login_failed',
  logout: 'auth.logout',
  predictionViewed: 'prediction.viewed',
  predictionCreated: 'prediction.created',
  scenarioCreated: 'scenario.created',
  scenarioDeleted: 'scenario.deleted',
  simulationStarted: 'simulation.started',
  recommendationApproved: 'recommendation.approved',
  recommendationExecuted: 'recommendation.executed',
  recommendationRejected: 'recommendation.rejected',
  alertAcknowledged: 'alert.acknowledged',
  alertAssigned: 'alert.assigned',
  alertResolved: 'alert.resolved',
  maintenanceScheduled: 'maintenance.scheduled',
  settingChanged: 'settings.changed',
  userChanged: 'user.changed',
  roleChanged: 'role.changed',
  demoStarted: 'demo.started',
  demoAdvanced: 'demo.advanced',
  copilotQuery: 'copilot.query',
  orchestrationRun: 'orchestrator.analysed',
  cascadeSimulated: 'cascade.simulated',
  counterfactualRun: 'counterfactual.searched',
  optimizationRun: 'optimization.ranked',
  stressTestRun: 'stress_test.run',
  decisionCreated: 'decision.created',
  decisionApproved: 'decision.approved',
  decisionRejected: 'decision.rejected',
  decisionSimulated: 'decision.simulated',
  twinSnapshot: 'twin.snapshot',
  twinSimulated: 'twin.simulated',
  resilienceTested: 'resilience.tested',
  adversarialRun: 'adversarial.searched',

  // ── 4.0 (§90: everything that produces or changes a claim is audited) ──────
  physicsValidated: 'physics.validated',
  verificationRecorded: 'verification.recorded',
  verificationClosed: 'verification.closed',
  discoveryRun: 'discovery.run',
  redBlueRun: 'resilience.red_blue',
  memoryOutcomeRecorded: 'memory.outcome_recorded',
  memoryLessonAdded: 'memory.lesson_added',
  learningReviewed: 'learning.reviewed',
  modelTransitioned: 'model.transitioned',
  paretoExplored: 'pareto.explored',
  evidenceBuilt: 'evidence.built',

  // ── 4.1 ────────────────────────────────────────────────────────────────────
  connectorCreated: 'integration.connector_created',
  connectorTested: 'integration.connector_tested',
  connectorSynced: 'integration.connector_synced',
  connectorMapped: 'integration.mapping_changed',
  connectorFaultInjected: 'integration.fault_injected',
  riskHuntRun: 'risk_hunter.run',
  riskPreventionFound: 'risk_hunter.prevention',
  proofCreated: 'proof.created',
  replayViewed: 'proof.replayed',
  // 4.2 — the Live 3D Grid (§106).
  liveGridOpened: 'live_grid.opened',
  liveGridAssetInspected: 'live_grid.asset_inspected',
  liveGridTimeMachine: 'live_grid.time_machine',
  liveGridSimulated: 'live_grid.simulation',
  liveGridTraced: 'live_grid.trace',
  // 5.0 — physics-grade analysis and the sensor layer (§33).
  contingencyRun: 'grid.contingency',
  futuresCompared: 'grid.compare_futures',
  batteryDispatchSimulated: 'storage.dispatch_simulated',
  sensorInspected: 'sensor.inspected',
  sensorFaultInjected: 'sensor.fault_injected',
} as const

export type AuditAction = (typeof AUDIT_ACTIONS)[keyof typeof AUDIT_ACTIONS]

export interface AuditInput {
  action: string
  entity: string
  entityId?: string | null
  user?: CurrentUser | null
  /** Used when there is no session yet — a failed login, for example. */
  userEmail?: string
  before?: unknown
  after?: unknown
  status?: 'success' | 'denied' | 'error'
  detail?: string
  request?: Request
}

/** Extracts a client IP, tolerating the usual proxy header shapes. */
export function clientIp(request?: Request): string | null {
  if (!request) return null
  const forwarded = request.headers.get('x-forwarded-for')
  if (forwarded) return forwarded.split(',')[0]!.trim()
  return request.headers.get('x-real-ip')
}

function serialise(value: unknown): string | null {
  if (value === undefined || value === null) return null
  try {
    return JSON.stringify(value)
  } catch {
    return null
  }
}

export async function writeAudit(input: AuditInput): Promise<void> {
  try {
    await prisma.auditLog.create({
      data: {
        userId: input.user?.id ?? null,
        userEmail: input.user?.email ?? input.userEmail ?? 'anonymous',
        userRole: input.user?.roles[0] ?? 'anonymous',
        action: input.action,
        entity: input.entity,
        entityId: input.entityId ?? null,
        ip: clientIp(input.request),
        userAgent: input.request?.headers.get('user-agent')?.slice(0, 300) ?? null,
        beforeJson: serialise(input.before),
        afterJson: serialise(input.after),
        status: input.status ?? 'success',
        detail: input.detail ?? null,
      },
    })
  } catch (error) {
    console.error('[audit] failed to record entry', input.action, error)
  }
}
