import 'server-only'

import { prisma } from '../db'
import { REGIONS } from '../data/regions'
import { sampleEnvironment } from '../engine/environment'
import { matchDna, type DnaMatch } from '../engine/dna'
import {
  environmentFor,
  extractAssetDna,
  resourcesFor,
  type AssetSnapshot,
  type DnaPatternRef,
} from '../engine/grid'
import { predictFailure, type PredictionResult } from '../engine/prediction'
import {
  buildStrategy,
  evaluateCandidates,
  generateCandidates,
  type EvaluatedAction,
  type StrategyEvaluation,
} from '../engine/actions'
import { remainingUsefulLifeDays, NO_PERTURBATION, type Perturbation } from '../engine/telemetry'
import { getGridSnapshot, loadDnaPatterns, loadRiskWeights } from './grid-service'

export interface AssetHistoryPoint {
  ts: number
  loadPct?: number
  tempC?: number
  voltageKv?: number
  currentA?: number
}

export interface AssetDetail {
  record: NonNullable<Awaited<ReturnType<typeof loadAssetRecord>>>
  live: AssetSnapshot
  prediction: PredictionResult
  dnaMatches: Array<DnaMatch<DnaPatternRef>>
  actions: EvaluatedAction[]
  strategy: { chosen: EvaluatedAction[]; evaluation: StrategyEvaluation }
  history: AssetHistoryPoint[]
  rulDays: number
  neighbours: AssetSnapshot[]
}

function loadAssetRecord(code: string) {
  return prisma.asset.findUnique({
    where: { code },
    include: {
      assetType: true,
      region: true,
      site: true,
      parent: { select: { code: true, name: true, nameAr: true } },
      sensors: { orderBy: { kind: 'asc' } },
      maintenance: { orderBy: { performedAt: 'desc' }, take: 12 },
      incidents: { orderBy: { startedAt: 'desc' }, take: 8 },
      predictions: {
        orderBy: { createdAt: 'desc' },
        take: 5,
        include: { factors: true, modelVersion: { select: { name: true, nameAr: true, version: true } } },
      },
      recommendations: { orderBy: { priority: 'asc' }, take: 8 },
      alerts: { orderBy: { createdAt: 'desc' }, take: 10 },
    },
  })
}

/**
 * Everything the Digital Asset Passport needs, in one pass.
 *
 * The prediction, the DNA match and the action list are all computed live rather than
 * read from the tables the seed wrote: a passport opened now should describe the asset
 * now, and the stored rows are a record of what was true when they were raised.
 */
export async function getAssetDetail(
  code: string,
  options: { ts?: number; perturbation?: Perturbation } = {},
): Promise<AssetDetail | null> {
  const ts = options.ts ?? Date.now()
  const perturbation = options.perturbation ?? NO_PERTURBATION

  const [record, snapshot, patterns, weights] = await Promise.all([
    loadAssetRecord(code),
    getGridSnapshot({ ts, perturbation }),
    loadDnaPatterns(),
    loadRiskWeights(),
  ])

  if (!record) return null

  const live = snapshot.assets.find((entry) => entry.profile.code === code)
  if (!live) return null

  const region = REGIONS.find((entry) => entry.code === live.profile.regionCode)!
  const envAt = environmentFor(region, perturbation)

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

  const regionSnapshot = snapshot.regions.find((entry) => entry.code === region.code)
  const currentDna = extractAssetDna({
    profile: live.profile,
    ts,
    sample: live.sample,
    env: envAt(ts),
    envAt,
    perturbation,
    neighbourStress: live.neighbourStress,
    renewableVariabilityPct: regionSnapshot?.renewableVariabilityPct ?? 0,
  })
  const dnaMatches = matchDna(currentDna, patterns, 3)

  const actionContext = {
    profile: live.profile,
    ts,
    envAt,
    perturbation,
    dnaSimilarity: live.dnaSimilarity,
    neighbourStress: live.neighbourStress,
    weights,
    resources: resourcesFor(live.profile, snapshot.assets),
    evaluateAtMin: prediction.etaMinutes ?? 45,
  }
  const actions = evaluateCandidates(actionContext, generateCandidates(actionContext))
  const strategy = buildStrategy(actionContext, actions, 30)

  const history = await getAssetHistory(record.id)

  return {
    record,
    live,
    prediction,
    dnaMatches,
    actions,
    strategy,
    history,
    rulDays: remainingUsefulLifeDays(live.profile, live.sample),
    neighbours: snapshot.assets.filter(
      (entry) =>
        entry.profile.regionCode === live.profile.regionCode &&
        entry.profile.assetTypeKey === live.profile.assetTypeKey &&
        entry.profile.code !== code,
    ),
  }
}

/**
 * Recorded telemetry for the charts.
 *
 * Read from `sensor_readings` rather than recomputed: the past is what was recorded, and
 * showing a freshly evaluated model in place of the record would quietly hide any drift
 * between the two. They agree here because the seed wrote them from the same model —
 * which is the point, and worth being able to verify.
 */
export async function getAssetHistory(
  assetId: string,
  hours = 48,
): Promise<AssetHistoryPoint[]> {
  const since = new Date(Date.now() - hours * 3_600_000)
  const rows = await prisma.sensorReading.findMany({
    where: {
      assetId,
      ts: { gte: since },
      kind: { in: ['load', 'temperature', 'voltage', 'current'] },
    },
    select: { ts: true, kind: true, value: true },
    orderBy: { ts: 'asc' },
  })

  const byTs = new Map<number, AssetHistoryPoint>()
  for (const row of rows) {
    const key = row.ts.getTime()
    const point = byTs.get(key) ?? { ts: key }
    if (row.kind === 'load') point.loadPct = row.value
    else if (row.kind === 'temperature') point.tempC = row.value
    else if (row.kind === 'voltage') point.voltageKv = row.value
    else if (row.kind === 'current') point.currentA = row.value
    byTs.set(key, point)
  }

  return [...byTs.values()].sort((a, b) => a.ts - b.ts)
}

/** Forward projection for the asset's own risk chart and the Time Machine. */
export function assetForwardSeries(prediction: PredictionResult) {
  return prediction.frames.map((frame) => ({
    ts: frame.ts,
    offsetMin: frame.offsetMin,
    risk: frame.risk.score,
    loadPct: frame.sample.loadPct,
    tempC: frame.sample.tempC,
    voltageKv: frame.sample.voltageKv,
    state: frame.state,
    breached: frame.breached,
  }))
}

export { sampleEnvironment }
