import 'server-only'

import { prisma } from '../db'
import { getGridSnapshot } from './grid-service'
import { forwardWalk } from '../engine/prediction'
import { sampleEnvironment } from '../engine/environment'
import { REGIONS } from '../data/regions'
import { round } from '../engine/rng'

/**
 * Prevention Score (§20) and the forward risk outlook.
 *
 * IMPORTANT (§20, §54): every figure here is a *demo estimate* derived from this build's
 * simulation, and the UI labels it as such. The point of computing rather than inventing
 * them is that the arithmetic is inspectable — each number below states exactly which
 * rows it counts and which coefficient it applies.
 */

/**
 * Grid-average emissions intensity used to convert avoided unserved energy into avoided
 * CO₂. A gas-dominated system sits near 0.55 t/MWh; the figure is a stated assumption,
 * not a measurement.
 */
const EMISSIONS_INTENSITY_T_PER_MWH = 0.55

/** Assumed value of unserved energy, for the economic impact estimate. */
export const VALUE_OF_LOST_LOAD_SAR_PER_MWH = 26_000

export interface PreventionScore {
  predictedRisks: number
  recommendations: number
  simulations: number
  potentialFailuresAvoided: number
  downtimeAvoidedHours: number
  customersProtected: number
  energyProtectedMwh: number
  co2AvoidedTons: number
  economicImpactSar: number
}

export async function getPreventionScore(): Promise<PreventionScore> {
  const [
    predictedRisks,
    recommendations,
    simulations,
    preventedPredictions,
    preventedIncidents,
    realisedIncidents,
  ] = await Promise.all([
    prisma.prediction.count(),
    prisma.recommendation.count(),
    prisma.simulation.count(),
    prisma.prediction.count({ where: { status: 'prevented' } }),
    prisma.incident.findMany({
      where: { wasPrevented: true },
      select: { customersAffected: true, nabdhLeadTimeMin: true, economicImpactSar: true },
    }),
    // Comparable events that *did* occur supply the average cost of not preventing one.
    prisma.incident.findMany({
      where: { wasPrevented: false },
      select: {
        durationMin: true,
        customersAffected: true,
        energyNotServedMwh: true,
        economicImpactSar: true,
      },
    }),
  ])

  const comparable = realisedIncidents.filter((incident) => incident.durationMin > 0)
  const count = Math.max(1, comparable.length)
  const averageOutageMin = comparable.reduce((sum, i) => sum + i.durationMin, 0) / count
  const averageCustomers = comparable.reduce((sum, i) => sum + i.customersAffected, 0) / count
  const averageEnergyMwh = comparable.reduce((sum, i) => sum + i.energyNotServedMwh, 0) / count

  // Each prevented event is credited with the average impact of a comparable event that
  // was not prevented — the counterfactual, stated plainly rather than assumed away.
  const avoidedEvents = preventedPredictions + preventedIncidents.length
  const downtimeAvoidedHours = round((avoidedEvents * averageOutageMin) / 60, 1)
  const customersProtected =
    preventedIncidents.reduce((sum, i) => sum + i.customersAffected, 0) +
    Math.round(preventedPredictions * averageCustomers)
  const energyProtectedMwh = round(avoidedEvents * averageEnergyMwh, 1)

  return {
    predictedRisks,
    recommendations,
    simulations,
    potentialFailuresAvoided: avoidedEvents,
    downtimeAvoidedHours,
    customersProtected,
    energyProtectedMwh,
    co2AvoidedTons: round(energyProtectedMwh * EMISSIONS_INTENSITY_T_PER_MWH, 1),
    economicImpactSar: round(energyProtectedMwh * VALUE_OF_LOST_LOAD_SAR_PER_MWH, 0),
  }
}

export interface RiskOutlookPoint {
  ts: number
  offsetMin: number
  peakRisk: number
  meanRisk: number
  assetsAtRisk: number
}

/**
 * The national risk outlook: the worst projected asset risk over the coming hours.
 *
 * Only the assets that are already interesting are walked forward. Projecting all 250
 * would multiply the work by twenty to discover that the quiet ones stay quiet — the
 * peak of the national curve can only come from an asset that is near the top today.
 */
export async function getRiskOutlook(
  horizonMin = 360,
  stepMin = 30,
): Promise<RiskOutlookPoint[]> {
  const snapshot = await getGridSnapshot()
  const candidates = [...snapshot.assets]
    .sort((a, b) => b.risk.score - a.risk.score)
    .slice(0, 12)

  if (candidates.length === 0) return []

  const walks = candidates.map((candidate) => {
    const region = REGIONS.find((entry) => entry.code === candidate.profile.regionCode)!
    return forwardWalk(candidate.profile, snapshot.ts, (ts) => sampleEnvironment(region, ts), {
      horizonMin,
      stepMin,
      dnaSimilarity: candidate.dnaSimilarity,
      neighbourStress: candidate.neighbourStress,
    })
  })

  const frameCount = walks[0].length
  const points: RiskOutlookPoint[] = []

  for (let index = 0; index < frameCount; index += 1) {
    const scores = walks.map((walk) => walk[index]?.risk.score ?? 0)
    points.push({
      ts: walks[0][index].ts,
      offsetMin: walks[0][index].offsetMin,
      peakRisk: round(Math.max(...scores), 1),
      meanRisk: round(scores.reduce((sum, value) => sum + value, 0) / scores.length, 1),
      assetsAtRisk: scores.filter((score) => score >= 55).length,
    })
  }

  return points
}
