import { AUDIT_ACTIONS, writeAudit } from '@/lib/audit'
import { apiRoute } from '@/lib/api/handler'
import { ApiError, ERROR_CODES } from '@/lib/api/errors'
import { toPerturbation } from '@/lib/api/overlay'
import { liveGridTimeMachineSchema, type LiveGridTimeMachineInput } from '@/lib/api/schemas'
import { perturbationFor, getFutureGrid, getLiveGrid } from '@/lib/services/live-grid-service'
import { scenarioFor } from '@/lib/engine/grid-scenarios'

export const dynamic = 'force-dynamic'

/**
 * Walk the scene forward.
 *
 * A projection, never an edit: the response carries the present *and* the requested
 * offset, and the present it carries is the unperturbed one. A time machine that mutated
 * the live state would make the grid's current reading depend on who last moved a slider.
 */
export const POST = apiRoute<Record<string, string>, LiveGridTimeMachineInput>(
  { permission: 'twin.read', bodySchema: liveGridTimeMachineSchema, limit: 60 },
  async ({ body, user, request }) => {
    const scenario = scenarioFor(body.scenario)

    // Resolve the scenario against the real topology first, so a storage dispatch lands on
    // a battery that exists.
    const base = await getLiveGrid({ twinCode: body.twinCode })
    if (!base) throw new ApiError(ERROR_CODES.notFound)

    const perturbation =
      toPerturbation(body.overlay) ??
      (scenario.key === 'normal' ? undefined : perturbationFor(scenario, base.assets))

    const future = await getFutureGrid({
      twinCode: body.twinCode,
      perturbation,
      scenario: body.scenario ?? null,
      offsets: body.offsets,
    })
    if (!future) throw new ApiError(ERROR_CODES.notFound)

    const offsetMin = body.offsetMin ?? 30
    const frame = future.frames.find((entry) => entry.offsetMin === offsetMin) ?? null

    await writeAudit({
      action: AUDIT_ACTIONS.liveGridTimeMachine,
      entity: 'live_grid',
      entityId: base.twin.code,
      user,
      request,
      detail: `+${offsetMin} min · ${scenario.key} · peak risk ${frame?.peakRisk ?? '—'}`,
    })

    return {
      // The present, unchanged. Named so it cannot be mistaken for the projection.
      present: { kind: future.present.kind, ts: future.present.ts, assets: future.present.assets },
      requestedOffsetMin: offsetMin,
      frame,
      frames: future.frames,
      scenario: scenario.key,
    }
  },
)
