import 'server-only'

import { buildGridSnapshot, type AssetSnapshot } from '../engine/grid'
import { simulateCascade } from '../engine/cascade'
import { buildRiskRadar, scoreFleet, weakestLink, type AssetCriticality, type RiskRadar } from '../engine/criticality'
import { round } from '../engine/rng'
import type { Perturbation } from '../engine/telemetry'
import { loadAssetProfiles, loadDnaPatterns, loadRiskWeights, getGridSnapshot } from './grid-service'
import { cascadeStates, loadGridGraph } from './graph-service'

/**
 * Grid Stress Test (§29), Find the Weak Link (§30) and the National Risk Radar (§32).
 *
 * The stress test raises regional demand in steps and re-runs the whole twin at each
 * one, watching for the first asset to give. That is a genuinely different question from
 * "what is at risk now": an asset can be perfectly comfortable today and still be the
 * first thing to fail when the grid is pushed, and knowing which one that is tells a
 * planner where the next reinforcement belongs.
 */

export interface StressStep {
  loadDeltaPct: number
  peakRisk: number
  stabilityIndex: number
  assetsOverLimit: number
  worstAssetCode: string
  worstAssetLoadPct: number
  totalLoadMw: number
}

export interface StressTestResult {
  regionCode: string | null
  steps: StressStep[]
  /** The first asset to exceed its limit as demand is raised. */
  weakestAssetCode: string | null
  weakestAssetName: string
  weakestAssetNameAr: string
  /** Demand increase, in per cent, at which the first limit is exceeded. */
  breakingPointPct: number | null
  /** National demand at the breaking point, in MW. */
  maximumSafeLoadMw: number
  failureSequence: Array<{ assetCode: string; offsetMin: number; outcome: string; probability: number }>
  headroomPct: number
}

/** Loading at which an asset is considered to have given way under the test. */
const LIMIT_PCT = 100

export interface StressTestOptions {
  regionCode?: string | null
  ts?: number
  maxDeltaPct?: number
  stepPct?: number
}

export async function runStressTest(options: StressTestOptions = {}): Promise<StressTestResult> {
  const ts = options.ts ?? Date.now()
  const maxDeltaPct = options.maxDeltaPct ?? 45
  const stepPct = options.stepPct ?? 5
  const regionCode = options.regionCode ?? null

  const [profiles, patterns, weights, graph] = await Promise.all([
    loadAssetProfiles(),
    loadDnaPatterns(),
    loadRiskWeights(),
    loadGridGraph(),
  ])

  const steps: StressStep[] = []
  let breakingPointPct: number | null = null
  let weakestAssetCode: string | null = null
  let maximumSafeLoadMw = 0
  let breakingSnapshotAssets: AssetSnapshot[] = []

  for (let delta = 0; delta <= maxDeltaPct; delta += stepPct) {
    const perturbation: Perturbation = delta === 0 ? {} : { loadDeltaPct: delta, regionCode }
    const snapshot = buildGridSnapshot(profiles, ts, { perturbation, patterns, weights })

    const scoped = regionCode
      ? snapshot.assets.filter((asset) => asset.profile.regionCode === regionCode)
      : snapshot.assets
    const overLimit = scoped.filter((asset) => Math.abs(asset.sample.loadPct) >= LIMIT_PCT)
    const worst = [...scoped].sort(
      (a, b) => Math.abs(b.sample.loadPct) - Math.abs(a.sample.loadPct),
    )[0]

    steps.push({
      loadDeltaPct: delta,
      peakRisk: round(Math.max(...scoped.map((asset) => asset.risk.score), 0), 1),
      stabilityIndex: round(snapshot.stabilityIndex, 1),
      assetsOverLimit: overLimit.length,
      worstAssetCode: worst?.profile.code ?? '',
      worstAssetLoadPct: round(Math.abs(worst?.sample.loadPct ?? 0), 1),
      totalLoadMw: round(snapshot.totalLoadMw, 0),
    })

    if (breakingPointPct === null && overLimit.length > 0) {
      breakingPointPct = delta
      // The weakest link under stress is whichever unit is furthest past its limit at
      // the step where the first one gives, not simply the first one alphabetically.
      weakestAssetCode = [...overLimit].sort(
        (a, b) => Math.abs(b.sample.loadPct) - Math.abs(a.sample.loadPct),
      )[0].profile.code
      breakingSnapshotAssets = snapshot.assets
    } else if (breakingPointPct === null) {
      maximumSafeLoadMw = round(snapshot.totalLoadMw, 0)
    }
  }

  let failureSequence: StressTestResult['failureSequence'] = []
  if (weakestAssetCode && breakingSnapshotAssets.length > 0) {
    const cascade = simulateCascade(
      graph,
      cascadeStates(breakingSnapshotAssets),
      weakestAssetCode,
      { horizonMin: 60 },
    )
    failureSequence = cascade.steps.map((step) => ({
      assetCode: step.assetCode,
      offsetMin: step.offsetMin,
      outcome: step.outcome,
      probability: step.probability,
    }))
  }

  const node = weakestAssetCode ? graph.byCode.get(weakestAssetCode) : null
  const baseLoad = steps[0]?.totalLoadMw ?? 0

  return {
    regionCode,
    steps,
    weakestAssetCode,
    weakestAssetName: node?.name ?? '',
    weakestAssetNameAr: node?.nameAr ?? '',
    breakingPointPct,
    maximumSafeLoadMw: maximumSafeLoadMw || baseLoad,
    failureSequence,
    headroomPct: breakingPointPct ?? maxDeltaPct,
  }
}

export interface WeakLinkResult {
  weakest: AssetCriticality | null
  ranked: AssetCriticality[]
  radar: RiskRadar
}

/**
 * Criticality across the fleet, with the weakest link picked out (§30, §31, §32).
 *
 * One snapshot, one scoring pass, three orderings — cheap enough to run on a page load,
 * which is what lets the radar be live rather than a nightly batch.
 */
export async function getWeakLinkAnalysis(
  options: { ts?: number; perturbation?: Perturbation; limit?: number } = {},
): Promise<WeakLinkResult> {
  const [snapshot, graph] = await Promise.all([
    getGridSnapshot({ ts: options.ts, perturbation: options.perturbation }),
    loadGridGraph(),
  ])

  const scores = scoreFleet(snapshot.assets, graph)
  return {
    weakest: weakestLink(scores),
    ranked: [...scores].sort((a, b) => b.exposureScore - a.exposureScore).slice(0, options.limit ?? 20),
    radar: buildRiskRadar(scores, snapshot.assets, { limit: 10 }),
  }
}
