/**
 * Root Cause AI (§18) and the causal chain behind it (§19).
 *
 * "Temperature high" is an observation, not a cause. It is also the point at which most
 * monitoring stops, which leaves the operator to do the actual reasoning under time
 * pressure. This module carries the reasoning one layer further back: it scores the
 * conditions that could produce the observed state, ranks them on measured evidence,
 * and lays the winner out as a chain — what changed, what that did to the asset, what
 * the asset is now heading towards, and who is behind it.
 *
 * Each candidate cause is scored from a real comparison against the digital twin's
 * expectation, so a cause that is not present scores zero rather than being suppressed
 * by hand.
 */

import type { EnvironmentSample } from './environment'
import type { AssetProfile, TelemetrySample } from './telemetry'
import type { PredictionResult } from './prediction'
import type { WeakSignalResult } from './weak-signal'
import { clamp, round } from './rng'

export const CAUSE_KEYS = [
  'extreme_heat',
  'demand_surge',
  'cooling_degradation',
  'maintenance_debt',
  'ageing',
  'neighbour_stress',
  'renewable_shortfall',
  'voltage_instability',
] as const
export type CauseKey = (typeof CAUSE_KEYS)[number]

export type CausalStage = 'cause' | 'condition' | 'event' | 'impact'

export interface CausalLink {
  stage: CausalStage
  key: string
  label: string
  labelAr: string
  detail: string
  detailAr: string
  value: number
  unit: string
}

export interface CauseCandidate {
  key: CauseKey
  label: string
  labelAr: string
  /** Strength of the evidence for this cause, 0–100. */
  score: number
  evidence: string
  evidenceAr: string
  value: number
  unit: string
}

export interface RootCauseResult {
  primary: CauseCandidate | null
  /** Contributing causes, strongest first, excluding the primary. */
  alternatives: CauseCandidate[]
  /** Everything with any evidence at all, for the evidence table. */
  candidates: CauseCandidate[]
  chain: CausalLink[]
  confidence: number
  summary: string
  summaryAr: string
  recommendedInvestigation: string
  recommendedInvestigationAr: string
}

export interface RootCauseInput {
  profile: AssetProfile
  sample: TelemetrySample
  baseline: TelemetrySample
  env: EnvironmentSample
  /** The same environment with the scenario removed — the seasonal normal. */
  envNormal: EnvironmentSample
  weakSignals: WeakSignalResult
  prediction: PredictionResult
  /** Days past the maintenance interval, 0 when up to date. */
  maintenanceDebtDays: number
  ageYears: number
  neighbourStress: number
  dnaSimilarity: number
  /** Regional renewable output relative to its forecast, in per cent. Negative = short. */
  renewableDeviationPct: number
}

const YEAR_DESIGN_LIFE = 35

function pct(value: number): string {
  return `${round(value, 1)}`
}

/**
 * Score every candidate cause from the evidence.
 *
 * Scores are deliberately not normalised to sum to 100: two causes can both be strongly
 * present, and forcing them to share a fixed budget would understate a genuine
 * coincidence of a heat wave *and* a demand surge — which is exactly the case that
 * matters most.
 */
function scoreCauses(input: RootCauseInput): CauseCandidate[] {
  const { profile, sample, baseline, env, envNormal, weakSignals } = input
  const candidates: CauseCandidate[] = []

  // ── Ambient heat above the seasonal normal ───────────────────────────────
  const heatDelta = env.tempC - envNormal.tempC
  const absoluteHeat = clamp((env.tempC - 38) / 12, 0, 1)
  if (heatDelta > 0.2 || absoluteHeat > 0) {
    candidates.push({
      key: 'extreme_heat',
      label: 'Ambient heat above the seasonal normal',
      labelAr: 'حرارة محيطة أعلى من المعدل الموسمي',
      score: round(clamp(clamp(heatDelta / 6, 0, 1) * 62 + absoluteHeat * 48, 0, 100), 1),
      evidence: `Ambient ${pct(env.tempC)} °C against a seasonal normal of ${pct(envNormal.tempC)} °C`,
      evidenceAr: `الحرارة المحيطة ${pct(env.tempC)} مئوية مقابل معدل موسمي ${pct(envNormal.tempC)} مئوية`,
      value: round(heatDelta, 2),
      unit: 'K',
    })
  }

  // ── Demand above what this asset does at this hour ───────────────────────
  const loadDelta = Math.abs(sample.loadPct) - Math.abs(baseline.loadPct)
  if (loadDelta > 0.5) {
    candidates.push({
      key: 'demand_surge',
      label: 'Demand above the expected profile for this hour',
      labelAr: 'طلب أعلى من المنحنى المتوقع لهذه الساعة',
      score: round(clamp((loadDelta / 22) * 100, 0, 100), 1),
      evidence: `Loading ${pct(Math.abs(sample.loadPct))} % against an expected ${pct(Math.abs(baseline.loadPct))} %`,
      evidenceAr: `التحميل ${pct(Math.abs(sample.loadPct))}٪ مقابل المتوقع ${pct(Math.abs(baseline.loadPct))}٪`,
      value: round(loadDelta, 2),
      unit: '%',
    })
  }

  // ── Cooling shedding less heat than it should ────────────────────────────
  const cooling = weakSignals.signals.find((s) => s.key === 'cooling')
  if (cooling && cooling.deviationPct > 0.5) {
    candidates.push({
      key: 'cooling_degradation',
      label: 'Cooling effectiveness below the modelled performance',
      labelAr: 'كفاءة التبريد أقل من الأداء المُنمذج',
      score: round(clamp((cooling.deviationPct / cooling.individualThresholdPct) * 78, 0, 100), 1),
      evidence: `Temperature rise per unit of loading is ${pct(cooling.deviationPct)} % above expectation`,
      evidenceAr: `الارتفاع الحراري لكل وحدة تحميل أعلى من المتوقع بنسبة ${pct(cooling.deviationPct)}٪`,
      value: round(cooling.deviationPct, 2),
      unit: '%',
    })
  }

  // ── Deferred maintenance ─────────────────────────────────────────────────
  if (input.maintenanceDebtDays > 0) {
    candidates.push({
      key: 'maintenance_debt',
      label: 'Maintenance overdue against the asset interval',
      labelAr: 'صيانة متأخرة عن دورة الأصل',
      score: round(clamp((input.maintenanceDebtDays / 240) * 100, 0, 92), 1),
      evidence: `${Math.round(input.maintenanceDebtDays)} days past the scheduled interval`,
      evidenceAr: `${Math.round(input.maintenanceDebtDays)} يومًا بعد الموعد المجدول`,
      value: round(input.maintenanceDebtDays, 0),
      unit: 'd',
    })
  }

  // ── Age and accumulated insulation wear ──────────────────────────────────
  if (input.ageYears > YEAR_DESIGN_LIFE * 0.45) {
    candidates.push({
      key: 'ageing',
      label: 'Accumulated insulation ageing',
      labelAr: 'تقادم متراكم في العزل',
      score: round(clamp(((input.ageYears - YEAR_DESIGN_LIFE * 0.45) / (YEAR_DESIGN_LIFE * 0.55)) * 72, 0, 82), 1),
      evidence: `${round(input.ageYears, 1)} years in service with a health score of ${pct(profile.healthScore)} %`,
      evidenceAr: `${round(input.ageYears, 1)} سنة في الخدمة بمؤشر صحة ${pct(profile.healthScore)}٪`,
      value: round(input.ageYears, 1),
      unit: 'y',
    })
  }

  // ── Neighbours already carrying more than usual ──────────────────────────
  if (input.neighbourStress > 0.12) {
    candidates.push({
      key: 'neighbour_stress',
      label: 'Neighbouring units already loaded, limiting transfer room',
      labelAr: 'الوحدات المجاورة محمّلة، مما يقلص مساحة التحويل',
      score: round(clamp(input.neighbourStress * 118, 0, 88), 1),
      evidence: `Neighbour stress index at ${pct(input.neighbourStress * 100)} %`,
      evidenceAr: `مؤشر إجهاد الجوار عند ${pct(input.neighbourStress * 100)}٪`,
      value: round(input.neighbourStress, 3),
      unit: '',
    })
  }

  // ── Renewable output short of forecast ───────────────────────────────────
  if (input.renewableDeviationPct < -1) {
    candidates.push({
      key: 'renewable_shortfall',
      label: 'Renewable output below forecast, shifting load onto the network',
      labelAr: 'إنتاج متجدد أقل من المتوقع، مما ينقل الحمل إلى الشبكة',
      score: round(clamp((Math.abs(input.renewableDeviationPct) / 30) * 84, 0, 90), 1),
      evidence: `Regional renewable output ${pct(Math.abs(input.renewableDeviationPct))} % below forecast`,
      evidenceAr: `إنتاج المتجددة الإقليمي أقل من المتوقع بنسبة ${pct(Math.abs(input.renewableDeviationPct))}٪`,
      value: round(input.renewableDeviationPct, 2),
      unit: '%',
    })
  }

  // ── Voltage moving away from nominal ─────────────────────────────────────
  if (Math.abs(sample.voltageDeviationPct) > 2.2) {
    candidates.push({
      key: 'voltage_instability',
      label: 'Voltage drifting from nominal under load',
      labelAr: 'انحراف الجهد عن الاسمي تحت الحمل',
      score: round(clamp(((Math.abs(sample.voltageDeviationPct) - 2) / 4) * 86, 0, 88), 1),
      evidence: `Voltage deviation at ${pct(sample.voltageDeviationPct)} % of nominal`,
      evidenceAr: `انحراف الجهد عند ${pct(sample.voltageDeviationPct)}٪ من الاسمي`,
      value: round(sample.voltageDeviationPct, 2),
      unit: '%',
    })
  }

  return candidates.filter((c) => c.score > 3).sort((a, b) => b.score - a.score)
}

/**
 * Build the four-stage chain (§19).
 *
 * The condition stage is the part that makes a chain a chain rather than a list: it
 * names the measured intermediate state through which the cause reaches the asset.
 */
function buildChain(input: RootCauseInput, top: CauseCandidate[]): CausalLink[] {
  const { profile, sample, baseline, prediction } = input
  const links: CausalLink[] = []

  for (const cause of top.slice(0, 3)) {
    links.push({
      stage: 'cause',
      key: cause.key,
      label: cause.label,
      labelAr: cause.labelAr,
      detail: cause.evidence,
      detailAr: cause.evidenceAr,
      value: cause.value,
      unit: cause.unit,
    })
  }

  const thermalRise = round(sample.tempC - baseline.tempC, 1)
  links.push({
    stage: 'condition',
    key: 'thermal_stress',
    label: 'Thermal stress above the modelled state',
    labelAr: 'إجهاد حراري أعلى من الحالة المُنمذجة',
    detail: `Top-oil temperature ${pct(sample.tempC)} °C, ${thermalRise >= 0 ? '+' : ''}${thermalRise} K against the twin, ${pct(profile.ratedTempC - sample.tempC)} K of margin left`,
    detailAr: `حرارة الزيت العلوي ${pct(sample.tempC)} مئوية، ${thermalRise >= 0 ? '+' : ''}${thermalRise} كلفن مقارنة بالتوأم الرقمي، ويتبقى ${pct(profile.ratedTempC - sample.tempC)} كلفن من الهامش`,
    value: thermalRise,
    unit: 'K',
  })

  links.push({
    stage: 'event',
    key: prediction.eventType,
    label: 'Predicted event',
    labelAr: 'الحدث المتوقع',
    detail:
      prediction.etaMinutes === null
        ? `Risk ${pct(prediction.peakRisk)} % at the horizon with no limit breach inside it`
        : `Limit breach in ${prediction.etaMinutes} minutes at ${pct(prediction.probability * 100)} % probability`,
    detailAr:
      prediction.etaMinutes === null
        ? `الخطر ${pct(prediction.peakRisk)}٪ عند الأفق دون تجاوز أي حد داخله`
        : `تجاوز الحد خلال ${prediction.etaMinutes} دقيقة باحتمال ${pct(prediction.probability * 100)}٪`,
    value: prediction.etaMinutes ?? prediction.peakRisk,
    unit: prediction.etaMinutes === null ? '%' : 'min',
  })

  links.push({
    stage: 'impact',
    key: 'customers',
    label: 'Exposure behind this asset',
    labelAr: 'التعرض خلف هذا الأصل',
    detail: `${profile.customersServed.toLocaleString('en-US')} customers and ${pct(profile.capacityMw)} MW of served capacity`,
    detailAr: `${profile.customersServed.toLocaleString('en-US')} مشترك و${pct(profile.capacityMw)} ميجاواط من السعة المخدومة`,
    value: profile.customersServed,
    unit: '',
  })

  return links
}

export function analyseRootCause(input: RootCauseInput): RootCauseResult {
  const candidates = scoreCauses(input)
  const primary = candidates[0] ?? null
  const alternatives = candidates.slice(1, 4)

  // Confidence rises when one cause clearly leads the field and when the evidence behind
  // it is strong; a two-way tie between weak candidates is honestly reported as such.
  const lead = primary ? primary.score - (alternatives[0]?.score ?? 0) : 0
  const confidence = primary
    ? round(clamp(38 + primary.score * 0.42 + clamp(lead / 30, 0, 1) * 22, 0, 96), 1)
    : 0

  const chain = primary ? buildChain(input, candidates) : []

  const summary = primary
    ? `${primary.label} is the strongest explanation for the current state of ${input.profile.code}${
        alternatives.length > 0 ? `, compounded by ${alternatives[0].label.toLowerCase()}` : ''
      }.`
    : `No condition on ${input.profile.code} deviates enough from the modelled baseline to explain an elevated state.`
  const summaryAr = primary
    ? `${primary.labelAr} هو التفسير الأقوى للحالة الحالية للأصل ${input.profile.code}${
        alternatives.length > 0 ? `، ويعززه ${alternatives[0].labelAr}` : ''
      }.`
    : `لا توجد حالة على الأصل ${input.profile.code} تنحرف بما يكفي عن الخط المرجعي المُنمذج لتفسير ارتفاع الحالة.`

  const investigation = primary ? INVESTIGATION[primary.key] : null

  return {
    primary,
    alternatives,
    candidates,
    chain,
    confidence,
    summary,
    summaryAr,
    recommendedInvestigation: investigation?.en ?? 'Continue routine monitoring; no targeted investigation is indicated.',
    recommendedInvestigationAr:
      investigation?.ar ?? 'استمر بالمراقبة الروتينية؛ لا يوجد فحص موجَّه مطلوب حاليًا.',
  }
}

/** What to actually go and check, per cause. */
const INVESTIGATION: Record<CauseKey, { en: string; ar: string }> = {
  extreme_heat: {
    en: 'Verify the ambient reading against the regional weather feed and confirm the cooling stages are all in service.',
    ar: 'تحقق من قراءة الحرارة المحيطة مقابل تغذية الطقس الإقليمية وتأكد من أن جميع مراحل التبريد في الخدمة.',
  },
  demand_surge: {
    en: 'Identify the feeder carrying the additional demand and confirm whether it is a transferred load or genuine growth.',
    ar: 'حدد المغذي الذي يحمل الطلب الإضافي وتأكد ممّا إذا كان حملًا محوَّلًا أم نموًا حقيقيًا.',
  },
  cooling_degradation: {
    en: 'Thermographic survey of the radiator bank and a check of pump and fan currents against their nameplate.',
    ar: 'مسح حراري لمجموعة المشعّات وفحص تيارات المضخات والمراوح مقارنة بلوحة البيانات.',
  },
  maintenance_debt: {
    en: 'Bring the overdue inspection forward, including dissolved-gas analysis on the main tank.',
    ar: 'قدّم موعد الفحص المتأخر، بما يشمل تحليل الغازات الذائبة في الخزان الرئيسي.',
  },
  ageing: {
    en: 'Review furan and moisture history to estimate remaining insulation life before the next peak season.',
    ar: 'راجع سجل الفيوران والرطوبة لتقدير العمر المتبقي للعزل قبل موسم الذروة القادم.',
  },
  neighbour_stress: {
    en: 'Re-balance the bus before the peak window so transfer capacity is available when it is needed.',
    ar: 'أعد موازنة القضيب قبل نافذة الذروة حتى تتوفر سعة التحويل عند الحاجة.',
  },
  renewable_shortfall: {
    en: 'Check the plant availability report and the cloud forecast for the coming three hours.',
    ar: 'راجع تقرير جاهزية المحطة وتوقعات الغيوم للساعات الثلاث القادمة.',
  },
  voltage_instability: {
    en: 'Inspect tap-changer position and reactive support status on the feeding busbar.',
    ar: 'افحص وضع مغيّر النقاط وحالة الدعم غير الفعّال على القضيب المغذي.',
  },
}
