import 'server-only'

import { prisma } from '../db'
import { evidenceHash, verifyEvidenceHash } from './sensor-service'
import {
  buildTimeline,
  compareDecisions,
  compareFutures,
  preventionWindow,
  type DecisionOption,
  type FutureComparison,
  type ReplayFrame,
  type ReplayTimeline,
} from '../engine/replay'
import { round } from '../engine/rng'
import { analyseAsset, type AssetIntelligence } from './intelligence-service'
import { buildEvidence } from './evidence-service'
import { contextFromIntelligence, findSimilarDecisions } from './memory-service'
import type { Perturbation } from '../engine/telemetry'

/**
 * Decision Proof and Replay.
 *
 * Two halves of one idea: a proof is the frozen record of how a recommendation was
 * reached, and a replay is that record played back on a clock.
 *
 * The design commitment that matters is that **a replay reads stored events rather than
 * recomputing them**. A replay that re-derived each frame would be a fresh analysis
 * wearing a timeline — it could disagree with the decision it claims to be replaying, and
 * would silently change every time the model did. Reading events means the replay is a
 * record.
 *
 * The evidence chain is not duplicated here. It lives in `EvidenceNode`/`EvidenceEdge`
 * under a bundle code, exactly where 4.0 put it; this service pins that bundle to a
 * decision, a model version and a snapshot so it can be produced months later.
 */

const MIN = 60_000

// ─────────────────────────────────────────────────────────────────────────────
// Building a proof
// ─────────────────────────────────────────────────────────────────────────────

export interface BuildProofInput {
  assetCode: string
  decisionCode?: string | null
  twinCode?: string | null
  ts?: number
  perturbation?: Perturbation
  userId?: string | null
  persist?: boolean
}

export interface ProofView {
  code: string
  assetCode: string
  decisionCode: string | null
  bundleCode: string
  modelVersion: string
  twinVersion: string
  dataSnapshotId: string
  twinSnapshotCode: string
  riskBefore: number
  riskAfter: number
  recommendation: string
  recommendationAr: string
  physicsVerdict: string
  weakestLinkKey: string
  weakestLinkScore: number
  measuredSharePct: number
  outcome: string
  evidenceKind: 'simulated' | 'observed'
  createdAt: number
  frameCount: number
}

/**
 * Freeze the current analysis into a proof, with its replay timeline.
 *
 * The timeline is generated from the *same* analysis that produced the recommendation, at
 * offsets derived from the prediction rather than invented: the weak signal sits where the
 * lead-time measurement puts it, and the modelled event sits at the predicted ETA. Placing
 * them at pretty round numbers would make the prevention window a caption.
 */
export async function buildProof(input: BuildProofInput): Promise<ProofView | null> {
  const ts = input.ts ?? Date.now()

  const intel = await analyseAsset(input.assetCode, {
    ts,
    perturbation: input.perturbation,
    includeLeadTime: true,
  })
  if (!intel) return null

  const evidence = await buildEvidence({
    assetCode: input.assetCode,
    ts,
    perturbation: input.perturbation,
    twinCode: input.twinCode ?? null,
    persist: input.persist !== false,
  })
  if (!evidence) return null

  const decision = input.decisionCode
    ? await prisma.decisionRecord.findUnique({ where: { code: input.decisionCode } })
    : null

  const recommended = intel.orchestration.recommended
  const code = `PRF-${Date.now().toString(36).toUpperCase()}-${input.assetCode}`
  const frames = timelineFrames(intel, ts)

  if (input.persist === false) {
    return {
      code,
      assetCode: input.assetCode,
      decisionCode: decision?.code ?? null,
      bundleCode: evidence.bundle.code,
      modelVersion: evidence.reproducibility.modelVersion,
      twinVersion: evidence.reproducibility.twinVersion,
      dataSnapshotId: evidence.reproducibility.dataSnapshotId,
      twinSnapshotCode: input.twinCode ?? '',
      riskBefore: round(recommended?.evaluation.riskBefore ?? intel.orchestration.unifiedRisk, 1),
      riskAfter: round(recommended?.evaluation.riskAfter ?? intel.orchestration.unifiedRisk, 1),
      recommendation: recommended?.label ?? '',
      recommendationAr: recommended?.labelAr ?? '',
      physicsVerdict: intel.physics.verdict,
      weakestLinkKey: evidence.bundle.weakestLink?.key ?? '',
      weakestLinkScore: evidence.bundle.weakestLink?.confidence ?? 0,
      measuredSharePct: evidence.bundle.measuredSharePct,
      outcome: 'pending',
      evidenceKind: 'simulated',
      createdAt: ts,
      frameCount: frames.length,
    }
  }

  // ── The electrical state this proof rests on (§26) ────────────────────────
  //
  // Solved rather than described: a proof that recorded "the physics were checked" without
  // the voltages it was checked at would be an assurance, not evidence. The solve is scoped
  // to the electrical area holding this asset, and its convergence is stored with it — a
  // proof built on a case that did not converge says so.
  const { solveGridFlow } = await import('./physics-grid-service')
  const flow = await solveGridFlow({
    twinCode: input.twinCode ?? undefined,
    ts,
    mode: 'ac',
    perturbation: input.perturbation,
  }).catch(() => null)

  const acBus = flow?.ac?.buses.find((bus) => bus.code === input.assetCode) ?? null
  const acBranch =
    flow?.ac?.branches.find(
      (branch) => branch.from === input.assetCode || branch.to === input.assetCode,
    ) ?? null

  const eventRows = frames.map((frame) => ({
    seq: frame.seq,
    ts: new Date(frame.ts),
    stage: frame.stage,
    title: frame.title,
    titleAr: frame.titleAr,
    detail: frame.detail,
    detailAr: frame.detailAr,
    riskScore: frame.riskScore,
    loadPct: frame.loadPct,
    tempC: frame.tempC,
    state: frame.state,
    sourceType: frame.sourceType,
    confidence: frame.confidence,
    // The solved state, stored so replay reads it rather than recomputing it (§27).
    voltagePu: acBus?.vPu ?? 0,
    angleDeg: acBus?.angleDeg ?? 0,
    activeMw: acBus?.pMw ?? 0,
    reactiveMvar: acBus?.qMvar ?? 0,
    currentA: acBranch ? round(acBranch.currentKa * 1_000, 1) : 0,
    loadingPct: acBranch?.loadingPct ?? 0,
    hasPhysics: Boolean(acBus),
  }))

  const frozen = {
    code,
    assetCode: input.assetCode,
    bundleCode: evidence.bundle.code,
    dataSnapshotId: evidence.reproducibility.dataSnapshotId,
    modelVersion: evidence.reproducibility.modelVersion,
    twinVersion: evidence.reproducibility.twinVersion,
    riskBefore: round(recommended?.evaluation.riskBefore ?? intel.orchestration.unifiedRisk, 1),
    riskAfter: round(recommended?.evaluation.riskAfter ?? intel.orchestration.unifiedRisk, 1),
    physicsVerdict: intel.physics.verdict,
    weakestLinkKey: evidence.bundle.weakestLink?.key ?? '',
    weakestLinkScore: evidence.bundle.weakestLink?.confidence ?? 0,
    measuredSharePct: evidence.bundle.measuredSharePct,
    flowMethod: flow?.ac ? 'newton_raphson' : 'none',
    flowConverged: flow?.ac?.converged ?? false,
    flowIterations: flow?.ac?.iterations ?? 0,
    flowConvergence: flow?.ac?.convergence ?? 0,
    impedanceSource: flow?.impedanceSource ?? 'typical',
    events: eventRows.map((event) => ({
      seq: event.seq,
      stage: event.stage,
      ts: event.ts.getTime(),
      riskScore: event.riskScore,
      loadPct: event.loadPct,
      tempC: event.tempC,
      voltagePu: event.voltagePu,
      angleDeg: event.angleDeg,
      activeMw: event.activeMw,
      reactiveMvar: event.reactiveMvar,
    })),
  }

  const row = await prisma.decisionProof.create({
    data: {
      code,
      decisionId: decision?.id ?? null,
      assetCode: input.assetCode,
      bundleCode: evidence.bundle.code,
      predictionId: '',
      simulationId: recommended ? `sim:${recommended.key}` : '',
      twinSnapshotCode: input.twinCode ?? '',
      dataSnapshotId: evidence.reproducibility.dataSnapshotId,
      modelVersion: evidence.reproducibility.modelVersion,
      twinVersion: evidence.reproducibility.twinVersion,
      riskBefore: round(recommended?.evaluation.riskBefore ?? intel.orchestration.unifiedRisk, 1),
      riskAfter: round(recommended?.evaluation.riskAfter ?? intel.orchestration.unifiedRisk, 1),
      recommendation: recommended?.label ?? '',
      recommendationAr: recommended?.labelAr ?? '',
      physicsVerdict: intel.physics.verdict,
      weakestLinkKey: evidence.bundle.weakestLink?.key ?? '',
      weakestLinkScore: evidence.bundle.weakestLink?.confidence ?? 0,
      measuredSharePct: evidence.bundle.measuredSharePct,
      outcome: 'pending',
      // Never anything else in this build. A proof of a simulated outcome labelled
      // observed would be the exact claim the truth rules forbid.
      evidenceKind: 'simulated',
      flowMethod: frozen.flowMethod,
      flowConverged: frozen.flowConverged,
      flowIterations: frozen.flowIterations,
      flowConvergence: frozen.flowConvergence,
      impedanceSource: frozen.impedanceSource,
      forecastVersion: evidence.reproducibility.modelVersion,
      topologyVersion: evidence.reproducibility.twinVersion,
      // Computed over the frozen content above, so the digest covers exactly what is
      // stored and nothing that varies between reads.
      evidenceHash: evidenceHash(frozen),
      createdById: input.userId ?? null,
      createdAt: new Date(ts),
      events: { create: eventRows },
    },
    include: { _count: { select: { events: true } } },
  })

  return {
    code: row.code,
    assetCode: row.assetCode,
    decisionCode: decision?.code ?? null,
    bundleCode: row.bundleCode,
    modelVersion: row.modelVersion,
    twinVersion: row.twinVersion,
    dataSnapshotId: row.dataSnapshotId,
    twinSnapshotCode: row.twinSnapshotCode,
    riskBefore: row.riskBefore,
    riskAfter: row.riskAfter,
    recommendation: row.recommendation,
    recommendationAr: row.recommendationAr,
    physicsVerdict: row.physicsVerdict,
    weakestLinkKey: row.weakestLinkKey,
    weakestLinkScore: row.weakestLinkScore,
    measuredSharePct: row.measuredSharePct,
    outcome: row.outcome,
    evidenceKind: 'simulated',
    createdAt: row.createdAt.getTime(),
    frameCount: row._count.events,
  }
}

/**
 * The incident as a sequence of moments.
 *
 * Offsets come from the analysis: the weak signal sits at the measured lead time before
 * the modelled event, the prediction at the moment the analysis ran, and the outcome at
 * the predicted ETA. When the lead time was not measured the signal sits a short way
 * before the analysis — but the prevention window is then computed from whatever is
 * actually there, so it never claims a lead that was not measured.
 */
export function timelineFrames(intel: AssetIntelligence, ts: number): ReplayFrame[] {
  const leadMin = intel.leadTime?.leadMinutes ?? 0
  const etaMin = intel.prediction.etaMinutes ?? 45
  const recommended = intel.orchestration.recommended
  const sample = intel.focus.sample

  // The analysis instant is the anchor. Everything before it is when the platform first
  // had something to say; everything after is what it expects.
  const signalAt = ts - Math.max(3, leadMin) * MIN
  const stateOf = (risk: number) =>
    risk >= 85 ? 'critical' : risk >= 65 ? 'warning' : risk >= 45 ? 'watch' : 'normal'

  const frames: Array<Omit<ReplayFrame, 'seq'>> = [
    {
      ts: signalAt - 6 * MIN,
      stage: 'observation',
      title: 'Grid within its envelope',
      titleAr: 'الشبكة ضمن مظروفها',
      detail: `${intel.assetCode} at ${round(Math.abs(sample.loadPct), 1)} % of nameplate`,
      detailAr: `${intel.assetCode} عند ${round(Math.abs(sample.loadPct), 1)}٪ من اللوحة الاسمية`,
      riskScore: round(Math.max(0, intel.prediction.currentRisk - 30), 1),
      loadPct: round(Math.abs(sample.loadPct) - 8, 1),
      tempC: round(sample.tempC - 6, 1),
      state: 'normal',
      sourceType: 'synthetic',
      confidence: 95,
    },
    {
      ts: signalAt,
      stage: 'weak_signal',
      title: 'Weak signals converge',
      titleAr: 'تقارب الإشارات الضعيفة',
      detail: `Combined weak-signal score ${round(intel.weakSignals.combinedScore, 0)} with no threshold crossed`,
      detailAr: `درجة الإشارة الضعيفة المجمعة ${round(intel.weakSignals.combinedScore, 0)} دون تجاوز أي عتبة`,
      riskScore: round(Math.max(0, intel.prediction.currentRisk - 18), 1),
      loadPct: round(Math.abs(sample.loadPct) - 4, 1),
      tempC: round(sample.tempC - 3, 1),
      state: 'watch',
      sourceType: 'estimated',
      confidence: round(intel.weakSignals.combinedScore, 1),
    },
  ]

  const dna = intel.dnaMatches[0]
  if (dna) {
    frames.push({
      ts: signalAt + 4 * MIN,
      stage: 'dna',
      title: `Failure DNA ${dna.pattern.code}`,
      titleAr: `بصمة العطل ${dna.pattern.code}`,
      detail: `Matched at ${round(dna.similarity, 1)} %`,
      detailAr: `تطابق بنسبة ${round(dna.similarity, 1)}٪`,
      riskScore: round(Math.max(0, intel.prediction.currentRisk - 8), 1),
      loadPct: round(Math.abs(sample.loadPct) - 2, 1),
      tempC: round(sample.tempC - 1.5, 1),
      state: 'watch',
      sourceType: 'estimated',
      confidence: round(dna.similarity, 1),
    })
  }

  frames.push(
    {
      ts,
      stage: 'prediction',
      title: 'Prediction generated',
      titleAr: 'إصدار التوقع',
      detail:
        intel.prediction.etaMinutes === null
          ? `Peak risk ${round(intel.prediction.peakRisk, 1)} %, no limit crossing predicted`
          : `Limit crossing in ~${round(intel.prediction.etaMinutes, 0)} min`,
      detailAr:
        intel.prediction.etaMinutes === null
          ? `ذروة الخطر ${round(intel.prediction.peakRisk, 1)}٪ دون تجاوز حد متوقع`
          : `تجاوز الحد خلال ~${round(intel.prediction.etaMinutes, 0)} دقيقة`,
      riskScore: round(intel.prediction.currentRisk, 1),
      loadPct: round(Math.abs(sample.loadPct), 1),
      tempC: round(sample.tempC, 1),
      state: stateOf(intel.prediction.currentRisk),
      sourceType: 'predicted',
      confidence: round(intel.uncertainty.predictionConfidence, 1),
    },
    {
      ts: ts + 1 * MIN,
      stage: 'uncertainty',
      title: 'Confidence assessed',
      titleAr: 'تقييم الثقة',
      detail:
        intel.uncertainty.windowLowMinutes === null
          ? `Confidence ${round(intel.uncertainty.predictionConfidence, 0)} %`
          : `Window ${intel.uncertainty.windowLowMinutes}–${intel.uncertainty.windowHighMinutes} min, confidence ${round(intel.uncertainty.predictionConfidence, 0)} %`,
      detailAr:
        intel.uncertainty.windowLowMinutes === null
          ? `الثقة ${round(intel.uncertainty.predictionConfidence, 0)}٪`
          : `النافذة ${intel.uncertainty.windowLowMinutes}–${intel.uncertainty.windowHighMinutes} دقيقة، والثقة ${round(intel.uncertainty.predictionConfidence, 0)}٪`,
      riskScore: round(intel.prediction.currentRisk, 1),
      loadPct: round(Math.abs(sample.loadPct), 1),
      tempC: round(sample.tempC, 1),
      state: stateOf(intel.prediction.currentRisk),
      sourceType: 'estimated',
      confidence: round(intel.uncertainty.predictionConfidence, 1),
    },
    {
      ts: ts + 2 * MIN,
      stage: 'physics',
      title: 'Physics validation',
      titleAr: 'التحقق الفيزيائي',
      detail: intel.physics.summary,
      detailAr: intel.physics.summaryAr,
      riskScore: round(intel.prediction.currentRisk, 1),
      loadPct: round(Math.abs(sample.loadPct), 1),
      tempC: round(sample.tempC, 1),
      state: stateOf(intel.prediction.currentRisk),
      sourceType: 'estimated',
      confidence: round(intel.physics.confidence, 1),
    },
    {
      ts: ts + 4 * MIN,
      stage: 'cascade',
      title:
        intel.cascade.affectedAssets <= 1 ? 'Cascade contained at the origin' : 'Cascade path walked',
      titleAr: intel.cascade.affectedAssets <= 1 ? 'التسلسل محتوى عند المصدر' : 'تتبع مسار التسلسل',
      detail: `${intel.cascade.affectedAssets} asset(s) over ${intel.cascade.steps.length} step(s)`,
      detailAr: `${intel.cascade.affectedAssets} أصل عبر ${intel.cascade.steps.length} خطوة`,
      riskScore: round(intel.prediction.peakRisk * 0.9, 1),
      loadPct: round(Math.abs(sample.loadPct) + 2, 1),
      tempC: round(sample.tempC + 2, 1),
      state: stateOf(intel.prediction.peakRisk * 0.9),
      sourceType: 'simulated',
      confidence: 80,
    },
    {
      ts: ts + 6 * MIN,
      stage: 'strategies',
      title: 'Strategies generated',
      titleAr: 'توليد الاستراتيجيات',
      detail: `${intel.optimisation.strategies.length} candidates scored`,
      detailAr: `تقييم ${intel.optimisation.strategies.length} مرشحًا`,
      riskScore: round(intel.prediction.peakRisk * 0.9, 1),
      loadPct: round(Math.abs(sample.loadPct) + 2, 1),
      tempC: round(sample.tempC + 2, 1),
      state: stateOf(intel.prediction.peakRisk * 0.9),
      sourceType: 'simulated',
      confidence: 82,
    },
  )

  if (recommended) {
    frames.push(
      {
        ts: ts + 8 * MIN,
        stage: 'simulation',
        title: 'Strategy simulated on the twin',
        titleAr: 'محاكاة الاستراتيجية على التوأم',
        detail: `${recommended.label}: ${round(recommended.evaluation.riskBefore, 0)} % → ${round(recommended.evaluation.riskAfter, 0)} %`,
        detailAr: `${recommended.labelAr}: ${round(recommended.evaluation.riskBefore, 0)}٪ ← ${round(recommended.evaluation.riskAfter, 0)}٪`,
        riskScore: round(recommended.evaluation.riskAfter, 1),
        loadPct: round(recommended.evaluation.loadAfterPct, 1),
        tempC: round(recommended.evaluation.tempAfterC, 1),
        state: stateOf(recommended.evaluation.riskAfter),
        sourceType: 'simulated',
        confidence: round(recommended.confidence, 1),
      },
      {
        ts: ts + 10 * MIN,
        stage: 'recommendation',
        title: recommended.label,
        titleAr: recommended.labelAr,
        detail: 'Recommended for human approval',
        detailAr: 'موصى بها لاعتماد بشري',
        riskScore: round(recommended.evaluation.riskAfter, 1),
        loadPct: round(recommended.evaluation.loadAfterPct, 1),
        tempC: round(recommended.evaluation.tempAfterC, 1),
        state: stateOf(recommended.evaluation.riskAfter),
        sourceType: 'estimated',
        confidence: round(intel.orchestration.confidence.recommendation, 1),
      },
      {
        ts: ts + 12 * MIN,
        stage: 'decision',
        title: 'Awaiting operator decision',
        titleAr: 'بانتظار قرار المشغل',
        detail: 'The final operational decision remains with a person',
        detailAr: 'القرار التشغيلي النهائي يبقى بيد الإنسان',
        riskScore: round(recommended.evaluation.riskAfter, 1),
        loadPct: round(recommended.evaluation.loadAfterPct, 1),
        tempC: round(recommended.evaluation.tempAfterC, 1),
        state: stateOf(recommended.evaluation.riskAfter),
        sourceType: 'manual',
        confidence: 100,
      },
      {
        ts: ts + etaMin * MIN,
        stage: 'outcome',
        title: 'Simulated outcome at the predicted moment',
        titleAr: 'نتيجة محاكاة عند اللحظة المتوقعة',
        detail: `Risk ${round(recommended.evaluation.riskAfter, 0)} % with the strategy in force — a simulated result, not an observed one`,
        detailAr: `الخطر ${round(recommended.evaluation.riskAfter, 0)}٪ مع تطبيق الاستراتيجية — نتيجة محاكاة لا نتيجة ملاحظة`,
        riskScore: round(recommended.evaluation.riskAfter, 1),
        loadPct: round(recommended.evaluation.loadAfterPct, 1),
        tempC: round(recommended.evaluation.tempAfterC, 1),
        state: stateOf(recommended.evaluation.riskAfter),
        sourceType: 'simulated',
        confidence: round(recommended.confidence, 1),
      },
    )
  }

  return frames
    .sort((a, b) => a.ts - b.ts)
    .map((frame, index) => ({ ...frame, seq: index }))
}

// ─────────────────────────────────────────────────────────────────────────────
// Reading a proof and its replay
// ─────────────────────────────────────────────────────────────────────────────

export interface ProofIntegrity {
  /** False on proofs written before 5.0 — they verify as unhashed, not as tampered. */
  hashed: boolean
  valid: boolean
  computed: string
  expected: string
  note: string
}

/** The electrical state stored against one replay event. */
export interface FramePhysics {
  /** False when this quantity was not captured for the event. Not the same as zero. */
  hasPhysics: boolean
  voltagePu: number
  angleDeg: number
  activeMw: number
  reactiveMvar: number
  currentA: number
  loadingPct: number
}

export interface ReplayView {
  proof: ProofView
  /** Whether the record still matches the digest written with it. */
  integrity: ProofIntegrity
  /** How the electrical state behind this proof was obtained. */
  physics: {
    method: string
    converged: boolean
    iterations: number
    convergence: number
    impedanceSource: string
  }
  /** The stored state per event, keyed by sequence. Read, never recomputed. */
  framePhysics: Record<number, FramePhysics>
  timeline: ReplayTimeline
  preventionWindowMin: number | null
  futures: FutureComparison[]
  alternatives: ReturnType<typeof compareDecisions>
  similar: Array<{
    code: string
    title: string
    titleAr: string
    similarity: number
    outcomeStatus: string
    evidenceKind: string
    headline: string
    headlineAr: string
  }>
}

export async function getReplay(code: string): Promise<ReplayView | null> {
  const row = await prisma.decisionProof.findUnique({
    where: { code },
    include: { events: { orderBy: { seq: 'asc' } }, _count: { select: { events: true } } },
  })
  if (!row) return null

  const frames: ReplayFrame[] = row.events.map((event) => ({
    seq: event.seq,
    ts: event.ts.getTime(),
    stage: event.stage as ReplayFrame['stage'],
    title: event.title,
    titleAr: event.titleAr,
    detail: event.detail,
    detailAr: event.detailAr,
    riskScore: event.riskScore,
    loadPct: event.loadPct,
    tempC: event.tempC,
    state: event.state,
    sourceType: event.sourceType,
    confidence: event.confidence,
  }))

  const timeline = buildTimeline(frames)
  const outcomeFrame = timeline.outcome

  // ── Integrity (§26) ───────────────────────────────────────────────────────
  //
  // Recomputed from the stored record and compared against the digest written with it.
  // The reconstruction below must mirror `frozen` in `buildProof` exactly; a mismatch
  // there would report every proof as tampered, which is why both live in this file.
  const frozen = {
    code: row.code,
    assetCode: row.assetCode,
    bundleCode: row.bundleCode,
    dataSnapshotId: row.dataSnapshotId,
    modelVersion: row.modelVersion,
    twinVersion: row.twinVersion,
    riskBefore: row.riskBefore,
    riskAfter: row.riskAfter,
    physicsVerdict: row.physicsVerdict,
    weakestLinkKey: row.weakestLinkKey,
    weakestLinkScore: row.weakestLinkScore,
    measuredSharePct: row.measuredSharePct,
    flowMethod: row.flowMethod,
    flowConverged: row.flowConverged,
    flowIterations: row.flowIterations,
    flowConvergence: row.flowConvergence,
    impedanceSource: row.impedanceSource,
    events: row.events.map((event) => ({
      seq: event.seq,
      stage: event.stage,
      ts: event.ts.getTime(),
      riskScore: event.riskScore,
      loadPct: event.loadPct,
      tempC: event.tempC,
      voltagePu: event.voltagePu,
      angleDeg: event.angleDeg,
      activeMw: event.activeMw,
      reactiveMvar: event.reactiveMvar,
    })),
  }

  const verification = verifyEvidenceHash(frozen, row.evidenceHash)
  const integrity: ProofIntegrity = {
    hashed: row.evidenceHash.length > 0,
    valid: row.evidenceHash.length > 0 && verification.valid,
    computed: verification.computed,
    expected: row.evidenceHash,
    note:
      row.evidenceHash.length === 0
        ? 'This proof predates the integrity digest. It is unverified rather than altered.'
        : verification.valid
          ? 'The record matches the digest written with it. An integrity check, not a signature — it establishes that nothing changed, not who wrote it.'
          : 'The record no longer matches the digest written with it. Something has changed since it was frozen.',
  }

  const framePhysics: Record<number, FramePhysics> = {}
  for (const event of row.events) {
    framePhysics[event.seq] = {
      hasPhysics: event.hasPhysics,
      voltagePu: event.voltagePu,
      angleDeg: event.angleDeg,
      activeMw: event.activeMw,
      reactiveMvar: event.reactiveMvar,
      currentA: event.currentA,
      loadingPct: event.loadingPct,
    }
  }

  // Fresh analysis for the *alternatives*, which are a comparison rather than a record:
  // the question "what else could we have done" is asked now, against the strategies the
  // optimiser can still enumerate.
  const intel = await analyseAsset(row.assetCode)

  const futures = compareFutures({
    withoutIntervention: {
      risk: row.riskBefore,
      loadPct: timeline.frames.find((f) => f.stage === 'cascade')?.loadPct ?? 0,
      tempC: timeline.frames.find((f) => f.stage === 'cascade')?.tempC ?? 0,
      state: 'critical',
    },
    withStrategy: {
      risk: row.riskAfter,
      loadPct: outcomeFrame?.loadPct ?? 0,
      tempC: outcomeFrame?.tempC ?? 0,
      state: outcomeFrame?.state ?? 'normal',
    },
    observed: outcomeFrame
      ? { risk: outcomeFrame.riskScore, loadPct: outcomeFrame.loadPct, tempC: outcomeFrame.tempC, state: outcomeFrame.state }
      : null,
    // Never true in this build. Passing the flag rather than hard-coding the label keeps
    // the honest path available the moment real outcomes exist.
    observedIsReal: false,
  })

  const alternatives = intel
    ? compareDecisions(
        intel.optimisation.strategies.map<DecisionOption>((strategy) => ({
          key: strategy.key,
          label: strategy.label,
          labelAr: strategy.labelAr,
          riskAfter: round(strategy.evaluation.riskAfter, 1),
          costSar: round(strategy.costSar, 0),
          executionMin: strategy.executionMin,
          customerImpact: strategy.customerImpact,
          assetStress: round(Math.max(0, strategy.evaluation.loadAfterPct - 80), 1),
          batteryPct: 0,
          cascadeAvoided: strategy.evaluation.riskAfter < 45,
          confidence: round(strategy.confidence, 1),
        })),
        intel.optimisation.strategies.find((strategy) => strategy.isRecommended)?.key ?? '',
      )
    : compareDecisions([], '')

  const similar = intel
    ? (await findSimilarDecisions(contextFromIntelligence(intel), { limit: 3 })).map((match) => ({
        code: match.record.code,
        title: match.record.title,
        titleAr: match.record.titleAr,
        similarity: match.similarity,
        outcomeStatus: match.record.outcomeStatus,
        evidenceKind: match.record.evidenceKind,
        headline: match.headline,
        headlineAr: match.headlineAr,
      }))
    : []

  // The window is measured from the timeline, not asserted: first signal to the modelled
  // event. Where the timeline lacks one of the two ends it reports nothing rather than
  // zero, which is what makes the figure worth quoting.
  const eventTs = timeline.outcome?.ts ?? null

  return {
    integrity,
    physics: {
      method: row.flowMethod,
      converged: row.flowConverged,
      iterations: row.flowIterations,
      convergence: row.flowConvergence,
      impedanceSource: row.impedanceSource,
    },
    framePhysics,
    proof: {
      code: row.code,
      assetCode: row.assetCode,
      decisionCode: null,
      bundleCode: row.bundleCode,
      modelVersion: row.modelVersion,
      twinVersion: row.twinVersion,
      dataSnapshotId: row.dataSnapshotId,
      twinSnapshotCode: row.twinSnapshotCode,
      riskBefore: row.riskBefore,
      riskAfter: row.riskAfter,
      recommendation: row.recommendation,
      recommendationAr: row.recommendationAr,
      physicsVerdict: row.physicsVerdict,
      weakestLinkKey: row.weakestLinkKey,
      weakestLinkScore: row.weakestLinkScore,
      measuredSharePct: row.measuredSharePct,
      outcome: row.outcome,
      evidenceKind: row.evidenceKind === 'observed' ? 'observed' : 'simulated',
      createdAt: row.createdAt.getTime(),
      frameCount: row._count.events,
    },
    timeline,
    preventionWindowMin: preventionWindow(timeline, eventTs),
    futures,
    alternatives,
    similar,
  }
}

export async function listProofs(options: { limit?: number; assetCode?: string } = {}) {
  const rows = await prisma.decisionProof.findMany({
    where: options.assetCode ? { assetCode: options.assetCode } : undefined,
    orderBy: { createdAt: 'desc' },
    take: options.limit ?? 20,
    include: { _count: { select: { events: true } } },
  })

  return rows.map((row) => ({
    code: row.code,
    assetCode: row.assetCode,
    recommendation: row.recommendation,
    recommendationAr: row.recommendationAr,
    riskBefore: row.riskBefore,
    riskAfter: row.riskAfter,
    physicsVerdict: row.physicsVerdict,
    weakestLinkKey: row.weakestLinkKey,
    weakestLinkScore: row.weakestLinkScore,
    measuredSharePct: row.measuredSharePct,
    outcome: row.outcome,
    evidenceKind: row.evidenceKind,
    createdAt: row.createdAt.getTime(),
    frameCount: row._count.events,
  }))
}

