import 'server-only'

import { prisma } from '../db'
import type { OperatingEnvelope, PhysicsValidation } from '../engine/physics'
import type { UncertaintyResult } from '../engine/uncertainty'
import type { CascadeBreakpoint, CascadeTreeNode } from '../engine/cascade-tree'
import type { EvidenceBundle } from '../engine/evidence'
import { contextFromIntelligence, findSimilarDecisions } from './memory-service'
import { buildEvidence } from './evidence-service'
import { getVerificationReport } from './verification-service'
import { REGIONS } from '../data/regions'
import {
  DEMO_ASSET_CODE,
  DEMO_REGION_CODE,
  DEMO_STAGE_COUNT,
  DEMO_STAGES,
  demoPerturbation,
  demoTimestampFor,
  getDemoStage,
  type DemoStage,
} from '../demo/config'
import { matchDna, type DnaMatch } from '../engine/dna'
import { environmentFor, extractAssetDna, resourcesFor, type DnaPatternRef } from '../engine/grid'
import { predictFailure } from '../engine/prediction'
import {
  buildStrategy,
  compareStrategies,
  evaluateCandidates,
  evaluateStrategy,
  generateCandidates,
  type EvaluatedAction,
} from '../engine/actions'
import { describePrediction } from '../engine/explain'
import type { Perturbation } from '../engine/telemetry'
import { round } from '../engine/rng'
import { riskToState, type GridState, type Locale } from '../domain/enums'
import type { RiskAssessment } from '../engine/risk'
import { getGridSnapshot, loadDnaPatterns, loadRiskWeights } from './grid-service'
import { analyseAsset, futureBranches } from './intelligence-service'
import { findMinimalIntervention, type LeverDose } from '../engine/counterfactual'
import { buildLevers } from './counterfactual-service'
import type { WeakSignalResult } from '../engine/weak-signal'
import type { RootCauseResult } from '../engine/rootcause'
import type { CascadeResult } from '../engine/cascade'
import type { ScoredStrategy } from '../engine/optimize'
import type { FutureBranch } from '../engine/orchestrator'
import { VALUE_OF_LOST_LOAD_SAR_PER_MWH } from './prevention-service'
import { getHubOverview, type HubOverview } from './integration-service'
import { runHunt, type HuntResult } from './risk-hunter-service'
import { timelineFrames } from './proof-service'
import { buildTimeline, type ReplayTimeline } from '../engine/replay'

/**
 * Demo Mode (§29, §51).
 *
 * Every value below is computed by the same engines that serve the operational screens —
 * the only thing Demo Mode changes is *when* and *under what scenario* they are
 * evaluated. It runs on a pinned virtual clock so the summer-peak story reproduces in
 * any season, and it applies a ramping perturbation so the build-up an audience watches
 * across the first four stages is the model's own trajectory, not a slideshow.
 */

/**
 * What the hunt act shows when the search cannot run at all.
 *
 * Not a placeholder result — an empty one that says zero simulations were run, so a
 * failed search reads as a failed search rather than as a grid with nothing wrong.
 */
const EMPTY_HUNT: HuntResult = {
  runCode: null,
  kind: 'hidden_risk',
  mode: 'quick',
  scopeKind: 'region',
  scopeCode: DEMO_REGION_CODE,
  focusAsset: '',
  findings: [],
  weakestLink: null,
  breakpoints: [],
  simulationsRun: 0,
  budgetSimulations: 0,
  budgetExhausted: false,
  skippedImplausible: 0,
  durationMs: 0,
  summary: 'The search could not be run.',
  summaryAr: 'تعذر تشغيل البحث.',
  evidenceKind: 'simulated',
}

export interface DemoAssetState {
  code: string
  name: string
  nameAr: string
  regionName: string
  regionNameAr: string
  capacityMw: number
  ratedTempC: number
  healthScore: number
  customersServed: number
  loadPct: number
  loadMw: number
  tempC: number
  ambientC: number
  voltageKv: number
  frequencyHz: number
  effectiveHealth: number
  riskScore: number
  state: GridState
  risk: RiskAssessment
}

export interface DemoPrediction {
  probability: number
  confidence: number
  etaMinutes: number | null
  etaAt: number | null
  breachReason: string | null
  peakRisk: number
  eventType: string
  narrative: string
  trajectory: Array<{ ts: number; offsetMin: number; risk: number; loadPct: number; tempC: number; breached: boolean }>
  mitigated: Array<{ ts: number; offsetMin: number; risk: number }> | null
}

export interface DemoImpact {
  customersProtected: number
  downtimeAvoidedMin: number
  capacityProtectedMw: number
  energyProtectedMwh: number
  economicImpactSar: number
}

export interface DemoState {
  stage: DemoStage
  stageCount: number
  virtualTs: number
  running: boolean
  runCode: string | null
  asset: DemoAssetState
  grid: {
    totalLoadMw: number
    regionLoadMw: number
    stabilityIndex: number
    frequencyHz: number
    renewableSharePct: number
    regionState: GridState
    regionPeakRisk: number
    assetsAtRisk: number
    ambientC: number
  }
  dnaMatches: Array<{
    code: string
    name: string
    nameAr: string
    eventType: string
    similarity: number
    contributions: DnaMatch<DnaPatternRef>['contributions']
  }>
  prediction: DemoPrediction
  actions: EvaluatedAction[]
  strategy: {
    chosen: string[]
    riskBefore: number
    riskAfter: number
    loadBeforePct: number
    loadAfterPct: number
    tempBeforeC: number
    tempAfterC: number
    totalMw: number
    totalCostSar: number
  }
  comparison: ReturnType<typeof compareStrategies>
  impact: DemoImpact
  /** 2.0 analysis for the stages that show it (§44). */
  weakSignals: WeakSignalResult
  rootCause: RootCauseResult
  cascade: CascadeResult
  counterfactual: {
    achieved: boolean
    targetRisk: number
    riskBefore: number
    riskAfter: number
    totalCostSar: number
    doses: LeverDose[]
  }
  strategies: ScoredStrategy[]
  branches: FutureBranch[]
  /** 4.0 acts (§105): physics, uncertainty, break points, memory, evidence, verification. */
  envelope: OperatingEnvelope
  physics: PhysicsValidation
  uncertainty: UncertaintyResult
  cascadeTree: CascadeTreeNode | null
  breakpoints: CascadeBreakpoint[]
  similar: SimilarDecisionSummary[]
  evidence: EvidenceBundle | null
  verification: {
    verified: number
    pending: number
    meanAbsPctError: number
    coveragePct: number
    calibrationGap: number
    fidelityMean: number
    underpowered: boolean
  }
  /**
   * 4.1 acts: the inputs, the hunt and the replay.
   *
   * `hunt` and `replay` are computed with `persist: false` — a demonstration must exercise
   * the real engines, but it has no business writing findings into the record every time
   * somebody advances a slide.
   */
  sources: HubOverview
  hunt: HuntResult
  replay: ReplayTimeline
  unifiedRisk: number
  confidence: {
    recommendation: number
    data: number
    prediction: number
    simulation: number
    overall: number
  }
}

/** Average recorded outage duration — the counterfactual for a prevented event. */
async function averageOutageMinutes(): Promise<number> {
  const result = await prisma.incident.aggregate({
    _avg: { durationMin: true },
    where: { durationMin: { gt: 0 } },
  })
  return result._avg.durationMin ?? 180
}

export async function getDemoState(
  stageIndex: number,
  locale: Locale,
  options: { now?: number; runCode?: string | null; running?: boolean } = {},
): Promise<DemoState> {
  const now = options.now ?? Date.now()
  const stage = getDemoStage(stageIndex)
  const virtualTs = demoTimestampFor(stage, now)

  const basePerturbation = demoPerturbation(now)
  const region = REGIONS.find((entry) => entry.code === DEMO_REGION_CODE)!
  const envAt = environmentFor(region, basePerturbation)

  const [patterns, weights, record, avgOutageMin] = await Promise.all([
    loadDnaPatterns(),
    loadRiskWeights(),
    prisma.asset.findUnique({
      where: { code: DEMO_ASSET_CODE },
      include: { region: { select: { name: true, nameAr: true } } },
    }),
    averageOutageMinutes(),
  ])

  if (!record) throw new Error(`Demo asset ${DEMO_ASSET_CODE} is missing — run the seed`)

  // The unmitigated grid at this stage's moment.
  const snapshot = await getGridSnapshot({ ts: virtualTs, perturbation: basePerturbation })
  const target = snapshot.assets.find((entry) => entry.profile.code === DEMO_ASSET_CODE)!
  const regionSnapshot = snapshot.regions.find((entry) => entry.code === DEMO_REGION_CODE)!

  // Failure DNA, extracted with the shared extractor so it matches the library exactly.
  const currentDna = extractAssetDna({
    profile: target.profile,
    ts: virtualTs,
    sample: target.sample,
    env: envAt(virtualTs),
    envAt,
    perturbation: basePerturbation,
    neighbourStress: target.neighbourStress,
    renewableVariabilityPct: regionSnapshot.renewableVariabilityPct,
  })
  const matches = matchDna(currentDna, patterns, 3)

  const prediction = predictFailure(target.profile, virtualTs, envAt, {
    perturbation: basePerturbation,
    dnaSimilarity: target.dnaSimilarity,
    neighbourStress: target.neighbourStress,
    weights,
    horizonMin: 180,
    stepMin: 5,
  })

  const context = {
    profile: target.profile,
    ts: virtualTs,
    envAt,
    perturbation: basePerturbation,
    dnaSimilarity: target.dnaSimilarity,
    neighbourStress: target.neighbourStress,
    weights,
    resources: resourcesFor(target.profile, snapshot.assets),
    evaluateAtMin: prediction.etaMinutes ?? 45,
  }
  const actions = evaluateCandidates(context, generateCandidates(context))
  const strategy = buildStrategy(context, actions, 25)
  const bestSingle = actions.find((action) => action.applied.length > 0) ?? null
  const comparison = compareStrategies(context, bestSingle, strategy.evaluation.actions)

  // From stage 9 the strategy is in force, so the state shown is the mitigated one.
  const mitigatedPerturbation: Perturbation = {
    ...basePerturbation,
    actions: strategy.evaluation.actions,
  }
  const shownSnapshot = stage.actionsApplied
    ? await getGridSnapshot({ ts: virtualTs, perturbation: mitigatedPerturbation })
    : snapshot
  const shown = shownSnapshot.assets.find((entry) => entry.profile.code === DEMO_ASSET_CODE)!
  const shownRegion = shownSnapshot.regions.find((entry) => entry.code === DEMO_REGION_CODE)!

  // The mitigated trajectory, for the Time Machine comparison.
  const mitigatedTrajectory =
    strategy.evaluation.actions.length > 0
      ? predictFailure(target.profile, virtualTs, envAt, {
          perturbation: mitigatedPerturbation,
          dnaSimilarity: 0,
          neighbourStress: target.neighbourStress,
          weights,
          horizonMin: 180,
          stepMin: 5,
        }).frames.map((frame) => ({
          ts: frame.ts,
          offsetMin: frame.offsetMin,
          risk: frame.risk.score,
        }))
      : null

  // ── The 2.0 layer ────────────────────────────────────────────────────────
  // Run the same pipeline the AI Operations Room runs, at the demo's virtual instant
  // and under the demo's scenario. Sharing the pipeline rather than re-deriving it here
  // is what guarantees the demo and the console cannot tell two different stories about
  // the same asset at the same moment.
  const intel = await analyseAsset(DEMO_ASSET_CODE, {
    ts: virtualTs,
    perturbation: basePerturbation,
  })
  if (!intel) throw new Error(`Demo asset ${DEMO_ASSET_CODE} could not be analysed`)

  const branches = await futureBranches(intel)

  // The 4.1 acts. The hunt runs for real — the same engine the Risk Hunter page calls —
  // but with `persist: false`, because advancing a slide is not a decision to file a
  // finding. Its budget line is whatever this run actually spent.
  const [sources, hunt] = await Promise.all([
    getHubOverview(),
    runHunt({
      kind: 'hidden_risk',
      mode: 'quick',
      regionCode: DEMO_REGION_CODE,
      assetCode: null,
      persist: false,
      ts: virtualTs,
    }),
  ])

  // The smallest intervention that clears the watch band, measured on the same context
  // the recommendations panel uses.
  const targetRisk = 12
  const counterfactualResult = findMinimalIntervention(buildLevers(intel), {
    measure: (applied) => evaluateStrategy(context, applied).riskAfter,
    satisfied: (metric) => metric <= targetRisk,
    higherIsBetter: false,
  })

  // Avoided impact — labelled as a demo estimate wherever it is displayed.
  const prevented = strategy.evaluation.riskAfter < 45 && strategy.evaluation.riskBefore >= 70
  const downtimeAvoidedMin = prevented ? Math.round(avgOutageMin) : 0
  const energyProtectedMwh = prevented
    ? round((Math.max(0, target.sample.loadMw) * downtimeAvoidedMin) / 60, 1)
    : 0

  // ── 4.0 acts ──────────────────────────────────────────────────────────────
  //
  // Fetched only for the stages that show them. The evidence chain in particular is the
  // most expensive read in the platform, and paying for it on every stage transition
  // would put a visible pause into the middle of a presentation.
  const wantsMemory = stage.key === 'memory'
  const wantsEvidence = stage.key === 'evidence'
  const wantsVerification = stage.key === 'verification'

  const [similarMatches, evidenceView, verificationReport] = await Promise.all([
    wantsMemory ? findSimilarDecisions(contextFromIntelligence(intel), { limit: 3 }) : [],
    wantsEvidence
      ? buildEvidence({ assetCode: DEMO_ASSET_CODE, ts: virtualTs, perturbation: basePerturbation })
      : null,
    wantsVerification ? getVerificationReport({}) : null,
  ])

  return {
    stage,
    stageCount: DEMO_STAGE_COUNT,
    virtualTs,
    running: options.running ?? false,
    runCode: options.runCode ?? null,
    asset: {
      code: record.code,
      name: record.name,
      nameAr: record.nameAr,
      regionName: record.region.name,
      regionNameAr: record.region.nameAr,
      capacityMw: record.capacityMw,
      ratedTempC: record.ratedTempC,
      healthScore: record.healthScore,
      customersServed: record.customersServed,
      loadPct: shown.sample.loadPct,
      loadMw: shown.sample.loadMw,
      tempC: shown.sample.tempC,
      ambientC: shown.sample.ambientC,
      voltageKv: shown.sample.voltageKv,
      frequencyHz: shownSnapshot.frequencyHz,
      effectiveHealth: shown.sample.effectiveHealth,
      riskScore: shown.risk.score,
      state: riskToState(shown.risk.score),
      risk: shown.risk,
    },
    grid: {
      totalLoadMw: shownSnapshot.totalLoadMw,
      regionLoadMw: shownRegion.loadMw,
      stabilityIndex: shownSnapshot.stabilityIndex,
      frequencyHz: shownSnapshot.frequencyHz,
      renewableSharePct: shownSnapshot.renewableSharePct,
      regionState: shownRegion.state,
      regionPeakRisk: shownRegion.peakRisk,
      assetsAtRisk: shownRegion.assetsAtRisk,
      ambientC: round(shownRegion.env.tempC, 1),
    },
    dnaMatches: matches.map((match) => ({
      code: match.pattern.code,
      name: match.pattern.name,
      nameAr: match.pattern.nameAr,
      eventType: match.pattern.eventType,
      similarity: match.similarity,
      contributions: match.contributions,
    })),
    prediction: {
      probability: prediction.probability,
      confidence: prediction.confidence,
      etaMinutes: prediction.etaMinutes,
      etaAt: prediction.etaAt,
      breachReason: prediction.breachReason,
      peakRisk: prediction.peakRisk,
      eventType: prediction.eventType,
      narrative: describePrediction(
        {
          profile: target.profile,
          sample: target.sample,
          risk: target.risk,
          dnaSimilarity: target.dnaSimilarity,
          etaMinutes: prediction.etaMinutes,
          confidence: prediction.confidence,
        },
        locale,
      ),
      trajectory: prediction.frames.map((frame) => ({
        ts: frame.ts,
        offsetMin: frame.offsetMin,
        risk: frame.risk.score,
        loadPct: frame.sample.loadPct,
        tempC: frame.sample.tempC,
        breached: frame.breached,
      })),
      mitigated: mitigatedTrajectory,
    },
    actions,
    strategy: {
      chosen: strategy.chosen.map((action) => action.key),
      riskBefore: strategy.evaluation.riskBefore,
      riskAfter: strategy.evaluation.riskAfter,
      loadBeforePct: strategy.evaluation.loadBeforePct,
      loadAfterPct: strategy.evaluation.loadAfterPct,
      tempBeforeC: strategy.evaluation.tempBeforeC,
      tempAfterC: strategy.evaluation.tempAfterC,
      totalMw: strategy.evaluation.totalMw,
      totalCostSar: strategy.evaluation.totalCostSar,
    },
    comparison,
    impact: {
      customersProtected: prevented ? record.customersServed : 0,
      downtimeAvoidedMin,
      capacityProtectedMw: prevented ? round(record.capacityMw, 0) : 0,
      energyProtectedMwh,
      economicImpactSar: round(energyProtectedMwh * VALUE_OF_LOST_LOAD_SAR_PER_MWH, 0),
    },
    weakSignals: intel.weakSignals,
    rootCause: intel.rootCause,
    cascade: intel.cascade,
    counterfactual: {
      achieved: counterfactualResult.achieved,
      targetRisk,
      riskBefore: counterfactualResult.metricBefore,
      riskAfter: counterfactualResult.metricAfter,
      totalCostSar: counterfactualResult.totalCostSar,
      doses: counterfactualResult.doses,
    },
    strategies: intel.optimisation.strategies,
    envelope: intel.envelope,
    physics: intel.physics,
    uncertainty: intel.uncertainty,
    cascadeTree: intel.cascadeTree,
    breakpoints: intel.breakpoints,
    similar: similarMatches.map((match) => ({
      code: match.record.code,
      title: match.record.title,
      titleAr: match.record.titleAr,
      similarity: match.similarity,
      strategyLabel: match.record.strategyLabel,
      strategyLabelAr: match.record.strategyLabelAr,
      riskBefore: match.record.riskBefore,
      riskAfter: match.record.riskAfter,
      outcomeStatus: match.record.outcomeStatus,
      evidenceKind: match.record.evidenceKind,
      evidenceLabel: match.evidenceLabel,
      evidenceLabelAr: match.evidenceLabelAr,
      headline: match.headline,
      headlineAr: match.headlineAr,
      contributions: match.contributions,
    })),
    evidence: evidenceView?.bundle ?? null,
    verification: {
      verified: verificationReport?.quality.verifiedCount ?? 0,
      pending: verificationReport?.quality.pendingCount ?? 0,
      meanAbsPctError: verificationReport?.quality.meanAbsPctError ?? 0,
      coveragePct: verificationReport?.quality.coveragePct ?? 0,
      calibrationGap: verificationReport?.quality.calibrationGap ?? 0,
      fidelityMean: verificationReport?.quality.fidelityMean ?? 0,
      underpowered: verificationReport?.quality.underpowered ?? true,
    },
    branches,
    sources,
    hunt: hunt ?? EMPTY_HUNT,
    replay: buildTimeline(timelineFrames(intel, virtualTs)),
    unifiedRisk: intel.orchestration.unifiedRisk,
    confidence: {
      recommendation: intel.orchestration.confidence.recommendation,
      data: intel.orchestration.confidence.data,
      prediction: intel.orchestration.confidence.prediction,
      simulation: intel.orchestration.confidence.simulation,
      overall: intel.orchestration.confidence.overall,
    },
  }
}

/** Starts a run and records it, so a demonstration leaves an audit trail like any action. */
export async function startDemoRun(userId: string | null): Promise<string> {
  const code = `DEMO-${Date.now().toString(36).toUpperCase()}`
  await prisma.demoRun.create({
    data: { code, stage: 1, status: 'running', createdById: userId, assetCode: DEMO_ASSET_CODE },
  })
  return code
}

export async function advanceDemoRun(code: string, stage: number): Promise<void> {
  await prisma.demoRun.updateMany({
    where: { code },
    data: {
      stage,
      status: stage >= DEMO_STAGE_COUNT ? 'completed' : 'running',
      completedAt: stage >= DEMO_STAGE_COUNT ? new Date() : null,
    },
  })
}

export { DEMO_STAGES, DEMO_ASSET_CODE }

/**
 * A similarity match, flattened for the demo payload.
 *
 * Declared here rather than imported so the demo's wire format does not change shape
 * whenever the memory engine's internals do — a presentation that breaks because a
 * private field was renamed is a presentation that breaks on stage.
 */
export interface SimilarDecisionSummary {
  code: string
  title: string
  titleAr: string
  similarity: number
  strategyLabel: string
  strategyLabelAr: string
  riskBefore: number
  riskAfter: number
  outcomeStatus: string
  evidenceKind: string
  evidenceLabel: string
  evidenceLabelAr: string
  headline: string
  headlineAr: string
  contributions: Array<{
    key: string
    label: string
    labelAr: string
    weight: number
    agreement: number
    detail: string
    detailAr: string
  }>
}
