import 'server-only'

import { cache } from 'react'

import { prisma } from '../db'
import { ASSET_PROFILE_SELECT, toAssetProfile } from '../data/asset-profile'
import { REGIONS } from '../data/regions'
import {
  buildGridSnapshot,
  type DnaPatternRef,
  type GridSnapshot,
  type AssetSnapshot,
} from '../engine/grid'
import { DEFAULT_RISK_WEIGHTS, RISK_WEIGHT_SETTING_KEYS, type RiskWeights } from '../engine/risk'
import type { Perturbation } from '../engine/telemetry'
import type { AssetProfile } from '../engine/telemetry'

/**
 * Shared read path for everything that needs the live grid.
 *
 * Two layers of caching, for two different problems:
 *   • `cache()` de-duplicates within a single request, so a page that renders five
 *     panels reads the fleet once.
 *   • a short TTL cache de-duplicates *across* requests, because the asset table changes
 *     on the order of days while the Command Center polls every few seconds. Without it,
 *     every tick would re-read 250 rows to compute values that depend only on the clock.
 *
 * The snapshot itself is never cached: it is a function of time and must be recomputed.
 */

interface TtlEntry<T> {
  value: T
  expiresAt: number
}

const store = globalThis as unknown as {
  nabdhProfileCache?: TtlEntry<AssetProfile[]>
  nabdhPatternCache?: TtlEntry<DnaPatternRef[]>
  nabdhWeightCache?: TtlEntry<RiskWeights>
}

const PROFILE_TTL_MS = 20_000
const PATTERN_TTL_MS = 60_000
const WEIGHT_TTL_MS = 10_000

export const loadAssetProfiles = cache(async (): Promise<AssetProfile[]> => {
  const cached = store.nabdhProfileCache
  if (cached && cached.expiresAt > Date.now()) return cached.value

  const rows = await prisma.asset.findMany({ select: ASSET_PROFILE_SELECT })
  const profiles = rows.map(toAssetProfile)
  store.nabdhProfileCache = { value: profiles, expiresAt: Date.now() + PROFILE_TTL_MS }
  return profiles
})

export const loadDnaPatterns = cache(async (): Promise<DnaPatternRef[]> => {
  const cached = store.nabdhPatternCache
  if (cached && cached.expiresAt > Date.now()) return cached.value

  const rows = await prisma.failurePattern.findMany({ include: { dna: true } })
  const patterns: DnaPatternRef[] = rows
    .filter((row) => row.dna)
    .map((row) => ({
      code: row.dna!.code,
      name: row.name,
      nameAr: row.nameAr,
      eventType: row.eventType,
      severity: row.severity,
      occurredAt: row.occurredAt.getTime(),
      dna: {
        voltageDeviation: row.dna!.voltageDeviation,
        frequencyDeviation: row.dna!.frequencyDeviation,
        loadPercentage: row.dna!.loadPercentage,
        temperature: row.dna!.temperature,
        loadGrowthRate: row.dna!.loadGrowthRate,
        renewableVariability: row.dna!.renewableVariability,
        assetAge: row.dna!.assetAge,
        maintenanceScore: row.dna!.maintenanceScore,
        neighboringFailures: row.dna!.neighboringFailures,
        weatherTemperature: row.dna!.weatherTemperature,
      },
    }))

  store.nabdhPatternCache = { value: patterns, expiresAt: Date.now() + PATTERN_TTL_MS }
  return patterns
})

/** Risk weights, read from `app_settings` so operators can retune them (§42). */
export const loadRiskWeights = cache(async (): Promise<RiskWeights> => {
  const cached = store.nabdhWeightCache
  if (cached && cached.expiresAt > Date.now()) return cached.value

  const rows = await prisma.appSetting.findMany({ where: { groupKey: 'risk' } })
  const byKey = new Map(rows.map((row) => [row.key, Number(row.value)]))

  const weights: RiskWeights = { ...DEFAULT_RISK_WEIGHTS }
  for (const [field, settingKey] of Object.entries(RISK_WEIGHT_SETTING_KEYS)) {
    const value = byKey.get(settingKey)
    if (value !== undefined && Number.isFinite(value)) {
      weights[field as keyof RiskWeights] = value
    }
  }

  store.nabdhWeightCache = { value: weights, expiresAt: Date.now() + WEIGHT_TTL_MS }
  return weights
})

/** Invalidate the caches after a write that changes them. */
export function invalidateGridCaches(scope: 'profiles' | 'patterns' | 'weights' | 'all' = 'all') {
  if (scope === 'profiles' || scope === 'all') store.nabdhProfileCache = undefined
  if (scope === 'patterns' || scope === 'all') store.nabdhPatternCache = undefined
  if (scope === 'weights' || scope === 'all') store.nabdhWeightCache = undefined
}

export interface SnapshotOptions {
  ts?: number
  perturbation?: Perturbation
  includePatterns?: boolean
}

export async function getGridSnapshot(options: SnapshotOptions = {}): Promise<GridSnapshot> {
  const [profiles, patterns, weights, activeIncidents] = await Promise.all([
    loadAssetProfiles(),
    options.includePatterns === false ? Promise.resolve([]) : loadDnaPatterns(),
    loadRiskWeights(),
    prisma.incident.count({ where: { status: { not: 'closed' } } }),
  ])

  return buildGridSnapshot(profiles, options.ts ?? Date.now(), {
    perturbation: options.perturbation,
    patterns,
    weights,
    activeIncidents,
  })
}

/** A single asset's snapshot plus its regional siblings, which risk and actions need. */
export async function getAssetSnapshot(
  code: string,
  options: SnapshotOptions = {},
): Promise<{ snapshot: GridSnapshot; asset: AssetSnapshot | null }> {
  const snapshot = await getGridSnapshot(options)
  return {
    snapshot,
    asset: snapshot.assets.find((entry) => entry.profile.code === code) ?? null,
  }
}

export function regionMeta(code: string) {
  return REGIONS.find((region) => region.code === code) ?? null
}
