import 'server-only'

import { prisma } from '../db'
import { OBJECTIVE_PRESETS, type ScoredStrategy } from '../engine/optimize'
import type { Perturbation } from '../engine/telemetry'
import { analyseAsset, type AssetIntelligence } from './intelligence-service'

/**
 * Optimisation runs and the Self-Healing Lab (§11, §12).
 *
 * The lab's promise is narrow and worth stating plainly: several candidate recovery
 * strategies are constructed, each is simulated on the digital twin, and they are
 * compared on identical terms. Nothing here reaches a real grid — "execution" in this
 * build means running the chosen strategy through the twin once more and recording the
 * result for approval (§43).
 */

export interface SelfHealingOptions {
  ts?: number
  perturbation?: Perturbation
  objectiveKey?: string
}

export interface SelfHealingResult {
  assetCode: string
  objectiveKey: string
  strategies: ScoredStrategy[]
  best: ScoredStrategy | null
  riskBefore: number
  horizonMin: number
  intel: AssetIntelligence
}

export const OBJECTIVE_KEYS = Object.keys(OBJECTIVE_PRESETS)

export async function runSelfHealing(
  assetCode: string,
  options: SelfHealingOptions = {},
): Promise<SelfHealingResult | null> {
  const intel = await analyseAsset(assetCode, {
    ts: options.ts,
    perturbation: options.perturbation,
    objectiveKey: options.objectiveKey,
  })
  if (!intel) return null

  const baseline = intel.optimisation.strategies.find((strategy) => strategy.kind === 'none')

  return {
    assetCode,
    objectiveKey: intel.optimisation.objectiveKey,
    strategies: intel.optimisation.strategies,
    best: intel.optimisation.best,
    riskBefore: baseline?.evaluation.riskBefore ?? intel.focus.risk.score,
    horizonMin: intel.prediction.horizonMin,
    intel,
  }
}

/**
 * Persist a run and its candidates.
 *
 * The stored rows carry each strategy's individual objective terms, not only its final
 * score, because "why did this one win" is a question that gets asked after the fact and
 * cannot be answered from a single number.
 */
export async function persistOptimization(
  result: SelfHealingResult,
  options: { userId?: string | null; ts?: number } = {},
): Promise<string> {
  const ts = options.ts ?? Date.now()
  const code = `OPT-${ts.toString(36).toUpperCase()}-${result.assetCode}`
  const weights = OBJECTIVE_PRESETS[result.objectiveKey] ?? OBJECTIVE_PRESETS.balanced

  const run = await prisma.optimizationRun.create({
    data: {
      code,
      assetCode: result.assetCode,
      objectiveKey: result.objectiveKey,
      candidateCount: result.strategies.length,
      bestScore: result.best?.score ?? 0,
      horizonMin: result.horizonMin,
      weightsDigest: Object.entries(weights)
        .map(([key, value]) => `${key}=${value}`)
        .join(','),
      createdById: options.userId ?? null,
    },
  })

  let bestStrategyId: string | null = null
  for (const strategy of result.strategies) {
    const created = await prisma.strategy.create({
      data: {
        code: `${code}-${strategy.key}`,
        optimizationId: run.id,
        assetCode: result.assetCode,
        label: strategy.label,
        labelAr: strategy.labelAr,
        kind: strategy.kind,
        rank: strategy.rank,
        score: strategy.score,
        riskBefore: strategy.evaluation.riskBefore,
        riskAfter: strategy.evaluation.riskAfter,
        costSar: strategy.costSar,
        executionMin: strategy.executionMin,
        customerImpact: strategy.customerImpact,
        totalMw: strategy.evaluation.totalMw,
        confidence: strategy.confidence,
        isRecommended: strategy.isRecommended,
        actions: {
          create: strategy.sourceActions.map((action, index) => ({
            ordinal: index,
            actionType: action.actionType,
            actionKey: action.key,
            assetCode: result.assetCode,
            magnitudeMw: action.magnitudeMw,
            durationMin: action.durationMin,
            costSar: action.costEstimateSar,
            label: action.title,
            labelAr: action.titleAr,
          })),
        },
        results: {
          create: strategy.metrics.map((metric) => ({
            metricKey: metric.key,
            value: metric.value,
            normalised: metric.normalised,
            weight: metric.weight,
            unit: metric.unit,
          })),
        },
      },
    })
    if (strategy.isRecommended) bestStrategyId = created.id
  }

  if (bestStrategyId) {
    await prisma.optimizationRun.update({
      where: { id: run.id },
      data: { bestStrategyId },
    })
  }

  return code
}

export async function listOptimizationRuns(limit = 20) {
  return prisma.optimizationRun.findMany({
    orderBy: { createdAt: 'desc' },
    take: limit,
    include: {
      strategies: {
        orderBy: { rank: 'asc' },
        include: { actions: { orderBy: { ordinal: 'asc' } }, results: true },
      },
    },
  })
}

export async function getStrategy(code: string) {
  return prisma.strategy.findUnique({
    where: { code },
    include: { actions: { orderBy: { ordinal: 'asc' } }, results: true },
  })
}

/**
 * Prevention patterns (§35, §36).
 *
 * A strategy that measurably worked is written back as a reusable pattern, keyed by the
 * problem it solved and the conditions it solved it under. The next time the same shape
 * appears, `matchPreventionPatterns` surfaces it — which is the whole of the learning
 * loop in this build, and is deliberately a retrieval mechanism rather than a claim that
 * a model has been retrained on outcomes.
 */
export async function recordPreventionPattern(input: {
  problemKey: string
  problem: string
  problemAr: string
  strategyLabel: string
  strategyLabelAr: string
  eventType: string
  assetTypeKey: string
  riskBefore: number
  riskAfter: number
  loadPct: number
  tempC: number
  dnaSimilarity: number
  strategyId?: string | null
}): Promise<string> {
  const existing = await prisma.preventionPattern.findFirst({
    where: { problemKey: input.problemKey, eventType: input.eventType, assetTypeKey: input.assetTypeKey },
  })

  const reduction = Math.max(0, input.riskBefore - input.riskAfter)
  const successRate = Math.min(100, (reduction / Math.max(input.riskBefore, 1)) * 100)

  if (existing) {
    const times = existing.timesApplied + 1
    await prisma.preventionPattern.update({
      where: { id: existing.id },
      data: {
        timesApplied: times,
        // Running mean, so a pattern's record reflects every time it was applied rather
        // than only the most recent run.
        successRate: (existing.successRate * existing.timesApplied + successRate) / times,
        riskBefore: (existing.riskBefore * existing.timesApplied + input.riskBefore) / times,
        riskAfter: (existing.riskAfter * existing.timesApplied + input.riskAfter) / times,
        loadPct: (existing.loadPct * existing.timesApplied + input.loadPct) / times,
        tempC: (existing.tempC * existing.timesApplied + input.tempC) / times,
        dnaSimilarity: (existing.dnaSimilarity * existing.timesApplied + input.dnaSimilarity) / times,
      },
    })
    return existing.code
  }

  const count = await prisma.preventionPattern.count()
  const code = `P-${(101 + count).toString()}`
  await prisma.preventionPattern.create({
    data: {
      code,
      problemKey: input.problemKey,
      problem: input.problem,
      problemAr: input.problemAr,
      strategyLabel: input.strategyLabel,
      strategyLabelAr: input.strategyLabelAr,
      eventType: input.eventType,
      assetTypeKey: input.assetTypeKey,
      riskBefore: input.riskBefore,
      riskAfter: input.riskAfter,
      successRate,
      loadPct: input.loadPct,
      tempC: input.tempC,
      dnaSimilarity: input.dnaSimilarity,
      sourceStrategyId: input.strategyId ?? null,
    },
  })
  return code
}

/**
 * Patterns that match the situation in front of the operator, closest first.
 *
 * Similarity is measured on the conditions the pattern was recorded under — loading,
 * temperature and DNA proximity — so a pattern learned on a lightly loaded unit in
 * winter does not surface against a thermal event in August.
 */
export async function matchPreventionPatterns(
  input: { eventType: string; assetTypeKey: string; loadPct: number; tempC: number; dnaSimilarity: number },
  limit = 3,
) {
  const rows = await prisma.preventionPattern.findMany({
    where: { OR: [{ eventType: input.eventType }, { assetTypeKey: input.assetTypeKey }] },
    take: 40,
  })

  return rows
    .map((pattern) => {
      const loadDelta = Math.abs(pattern.loadPct - input.loadPct) / 40
      const tempDelta = Math.abs(pattern.tempC - input.tempC) / 30
      const dnaDelta = Math.abs(pattern.dnaSimilarity - input.dnaSimilarity) / 60
      const typeBonus = pattern.eventType === input.eventType ? 0 : 0.35
      const distance = loadDelta * 0.4 + tempDelta * 0.35 + dnaDelta * 0.25 + typeBonus
      return { pattern, similarity: Math.max(0, Math.round((1 - Math.min(distance, 1)) * 1000) / 10) }
    })
    .sort((a, b) => b.similarity - a.similarity)
    .slice(0, limit)
}
