import 'server-only'

import { prisma } from '../db'
import { evaluateStrategy } from '../engine/actions'
import { decisionConfidence } from '../engine/quality'
import { round } from '../engine/rng'
import type { AppliedAction } from '../engine/telemetry'
import { analyseAsset } from './intelligence-service'
import { recordPreventionPattern } from './optimization-service'

/**
 * Human-in-the-loop decisions (§13) and the decision audit trail (§41).
 *
 * The platform recommends; a person decides. That is not a limitation to be engineered
 * around, it is the design: every recommendation lands as a record with a pending
 * operator step and a pending supervisor step, and nothing progresses until a named
 * human moves it. In this build the final step is a simulation rather than a switching
 * order (§43) — the record says so explicitly, because a system that blurred that line
 * would be dangerous in exactly the setting it is meant for.
 */

export const APPROVAL_STEPS = ['operator', 'supervisor'] as const
export type ApprovalStep = (typeof APPROVAL_STEPS)[number]

export const DECISION_STATUSES = [
  'pending',
  'operator_approved',
  'approved',
  'rejected',
  'simulated',
  'withdrawn',
] as const
export type DecisionStatus = (typeof DECISION_STATUSES)[number]

export interface CreateDecisionInput {
  assetCode: string
  strategyCode?: string | null
  predictionId?: string | null
  orchestrationId?: string | null
  userId: string
  ts?: number
}

/**
 * Raise a decision from the current recommendation.
 *
 * The four confidences are captured at creation time and never recomputed: the question
 * an audit asks is what the operator was shown when they decided, not what the model
 * would say about it today.
 */
export async function createDecision(input: CreateDecisionInput): Promise<string | null> {
  const intel = await analyseAsset(input.assetCode, { ts: input.ts })
  if (!intel) return null

  const recommended = intel.orchestration.recommended
  const ts = input.ts ?? Date.now()
  const code = `DEC-${ts.toString(36).toUpperCase()}-${input.assetCode}`

  let strategyId: string | null = null
  if (input.strategyCode) {
    const strategy = await prisma.strategy.findUnique({
      where: { code: input.strategyCode },
      select: { id: true },
    })
    strategyId = strategy?.id ?? null
  }

  const confidence = intel.orchestration.confidence
  const modelVersion = await prisma.modelVersion.findFirst({
    where: { status: 'active' },
    select: { id: true },
    orderBy: { trainedAt: 'desc' },
  })

  await prisma.decisionRecord.create({
    data: {
      code,
      assetCode: input.assetCode,
      strategyId,
      predictionId: input.predictionId ?? null,
      orchestrationId: input.orchestrationId ?? null,
      modelVersionId: modelVersion?.id ?? null,
      title: recommended ? recommended.label : `Hold ${input.assetCode} under observation`,
      titleAr: recommended ? recommended.labelAr : `إبقاء ${input.assetCode} تحت المراقبة`,
      rationale: intel.orchestration.rationale,
      rationaleAr: intel.orchestration.rationaleAr,
      riskBefore: recommended?.evaluation.riskBefore ?? intel.focus.risk.score,
      riskAfter: recommended?.evaluation.riskAfter ?? intel.focus.risk.score,
      recommendationConfidence: confidence.recommendation,
      dataConfidence: confidence.data,
      predictionConfidence: confidence.prediction,
      simulationConfidence: confidence.simulation,
      status: 'pending',
      dataTs: new Date(intel.ts),
      createdById: input.userId,
      approvals: {
        create: APPROVAL_STEPS.map((step) => ({ step, decision: 'pending' })),
      },
    },
  })

  return code
}

export interface ApprovalInput {
  decisionCode: string
  step: ApprovalStep
  decision: 'approved' | 'rejected' | 'modified'
  comment?: string
  commentAr?: string
  user: { id: string; name: string; nameAr: string }
}

/**
 * Record one approval step and move the record's status.
 *
 * A rejection at either step ends the decision; a modification returns it to pending so
 * the amended strategy is reviewed from the start rather than inheriting an approval
 * that was given for something else.
 */
export async function decideApproval(input: ApprovalInput): Promise<DecisionStatus | null> {
  const record = await prisma.decisionRecord.findUnique({
    where: { code: input.decisionCode },
    include: { approvals: true },
  })
  if (!record) return null

  const approval = record.approvals.find((entry) => entry.step === input.step)
  if (!approval) return null

  await prisma.approval.update({
    where: { id: approval.id },
    data: {
      decision: input.decision,
      userId: input.user.id,
      userName: input.user.name,
      userNameAr: input.user.nameAr,
      comment: input.comment ?? '',
      commentAr: input.commentAr ?? '',
      decidedAt: new Date(),
    },
  })

  const approvals = await prisma.approval.findMany({ where: { decisionId: record.id } })
  let status: DecisionStatus = 'pending'
  if (approvals.some((entry) => entry.decision === 'rejected')) status = 'rejected'
  else if (approvals.some((entry) => entry.decision === 'modified')) status = 'pending'
  else if (approvals.every((entry) => entry.decision === 'approved')) status = 'approved'
  else if (approvals.find((entry) => entry.step === 'operator')?.decision === 'approved')
    status = 'operator_approved'

  await prisma.decisionRecord.update({ where: { id: record.id }, data: { status } })
  return status
}

export interface ExecutionOutcome {
  status: DecisionStatus
  riskBefore: number
  riskAfter: number
  outcome: string
  outcomeAr: string
  patternCode: string | null
}

/**
 * "Execute" an approved decision.
 *
 * In this build that means running the approved strategy through the digital twin once
 * more and recording the measured result. The wording of the stored outcome says so, so
 * a reader of the audit trail cannot mistake it for a switching order that was carried
 * out on a network (§43, §57).
 */
export async function executeDecision(
  decisionCode: string,
  options: { ts?: number } = {},
): Promise<ExecutionOutcome | null> {
  const record = await prisma.decisionRecord.findUnique({
    where: { code: decisionCode },
    include: { strategy: { include: { actions: true } } },
  })
  if (!record) return null
  if (record.status !== 'approved') {
    return {
      status: record.status as DecisionStatus,
      riskBefore: record.riskBefore,
      riskAfter: record.riskAfter,
      outcome: 'Decision is not approved; nothing was simulated.',
      outcomeAr: 'القرار غير معتمد؛ لم تُجرَ أي محاكاة.',
      patternCode: null,
    }
  }

  const intel = await analyseAsset(record.assetCode, { ts: options.ts })
  if (!intel) return null

  const actions: AppliedAction[] =
    record.strategy?.actions.map((action) => ({
      type: action.actionType,
      assetCode: action.assetCode,
      magnitudeMw: action.magnitudeMw,
    })) ?? intel.orchestration.recommended?.actions ?? []

  const evaluation = evaluateStrategy(intel.actionContext, actions)
  const outcome = `Simulated on the digital twin: risk ${round(evaluation.riskBefore, 1)} % → ${round(evaluation.riskAfter, 1)} %, loading ${round(evaluation.loadBeforePct, 1)} % → ${round(evaluation.loadAfterPct, 1)} %. No control action was issued to any network.`
  const outcomeAr = `مُحاكاة على التوأم الرقمي: الخطر ${round(evaluation.riskBefore, 1)}٪ ← ${round(evaluation.riskAfter, 1)}٪، والتحميل ${round(evaluation.loadBeforePct, 1)}٪ ← ${round(evaluation.loadAfterPct, 1)}٪. لم يُصدَر أي أمر تحكم إلى أي شبكة.`

  await prisma.decisionRecord.update({
    where: { id: record.id },
    data: { status: 'simulated', outcome, outcomeAr, riskAfter: evaluation.riskAfter },
  })

  // A strategy that measurably worked becomes a reusable pattern (§35).
  let patternCode: string | null = null
  if (evaluation.riskReduction > 5 && intel.rootCause.primary) {
    patternCode = await recordPreventionPattern({
      problemKey: intel.rootCause.primary.key,
      problem: intel.rootCause.primary.label,
      problemAr: intel.rootCause.primary.labelAr,
      strategyLabel: record.title,
      strategyLabelAr: record.titleAr,
      eventType: intel.prediction.eventType,
      assetTypeKey: intel.focus.profile.assetTypeKey,
      riskBefore: evaluation.riskBefore,
      riskAfter: evaluation.riskAfter,
      loadPct: evaluation.loadBeforePct,
      tempC: evaluation.tempBeforeC,
      dnaSimilarity: intel.focus.dnaSimilarity,
      strategyId: record.strategyId,
    })
  }

  return {
    status: 'simulated',
    riskBefore: round(evaluation.riskBefore, 1),
    riskAfter: round(evaluation.riskAfter, 1),
    outcome,
    outcomeAr,
    patternCode,
  }
}

export async function listDecisions(options: { limit?: number; status?: string } = {}) {
  return prisma.decisionRecord.findMany({
    where: options.status ? { status: options.status } : undefined,
    orderBy: { createdAt: 'desc' },
    take: options.limit ?? 25,
    include: {
      approvals: { orderBy: { createdAt: 'asc' } },
      strategy: { include: { actions: { orderBy: { ordinal: 'asc' } } } },
    },
  })
}

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

/** Confidence bundle for a stored decision, recomputed from its own recorded fields. */
export function storedConfidence(record: {
  recommendationConfidence: number
  dataConfidence: number
  predictionConfidence: number
  simulationConfidence: number
}) {
  return decisionConfidence({
    recommendation: record.recommendationConfidence,
    data: record.dataConfidence,
    prediction: record.predictionConfidence,
    simulation: record.simulationConfidence,
  })
}
