import 'server-only'

import { prisma } from '../db'
import { REGIONS } from '../data/regions'
import {
  buildKnowledgeGraph,
  neighbourhood,
  searchKnowledge,
  type KnowledgeGraphData,
  type KnowledgeNeighbourhood,
  type KnowledgeNode,
} from '../engine/knowledge'
import { getGridSnapshot } from './grid-service'

/**
 * The National Energy Knowledge Graph (§16, §17).
 *
 * Assembled from the register and the live snapshot rather than maintained by hand: an
 * asset that was commissioned yesterday is in the graph today, and a node's weight moves
 * with its risk. The concept layer is the only declared part, because physics does not
 * come out of a table.
 */

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

const store = globalThis as unknown as { nabdhKnowledgeCache?: TtlEntry<KnowledgeGraphData> }
const KNOWLEDGE_TTL_MS = 60_000

export async function loadKnowledgeGraph(options: { ts?: number } = {}): Promise<KnowledgeGraphData> {
  const cached = store.nabdhKnowledgeCache
  if (cached && cached.expiresAt > Date.now()) return cached.value

  const [snapshot, patterns, incidents, alerts, predictions] = await Promise.all([
    getGridSnapshot({ ts: options.ts }),
    prisma.failurePattern.findMany({
      select: { code: true, name: true, nameAr: true, eventType: true, severity: true },
      take: 30,
    }),
    prisma.incident.findMany({
      orderBy: { startedAt: 'desc' },
      take: 20,
      select: {
        code: true,
        title: true,
        titleAr: true,
        severity: true,
        asset: { select: { code: true } },
        region: { select: { code: true } },
      },
    }),
    prisma.alert.findMany({
      orderBy: { createdAt: 'desc' },
      take: 25,
      select: {
        code: true,
        title: true,
        titleAr: true,
        level: true,
        asset: { select: { code: true } },
      },
    }),
    prisma.prediction.findMany({
      orderBy: { createdAt: 'desc' },
      take: 25,
      select: {
        code: true,
        eventType: true,
        riskScore: true,
        dnaCode: true,
        asset: { select: { code: true } },
      },
    }),
  ])

  const graph = buildKnowledgeGraph({
    regions: REGIONS.map((region) => ({
      code: region.code,
      name: region.name,
      nameAr: region.nameAr,
      peakDemandMw: region.peakDemandMw,
      climate: region.climate,
      climateAr: region.climateAr,
    })),
    assets: snapshot.assets.map((asset) => ({
      code: asset.profile.code,
      name: asset.profile.code,
      nameAr: asset.profile.code,
      assetTypeKey: asset.profile.assetTypeKey,
      regionCode: asset.profile.regionCode,
      capacityMw: asset.profile.capacityMw,
      customersServed: asset.profile.customersServed,
      riskScore: asset.risk.score,
      healthScore: asset.sample.effectiveHealth,
    })),
    patterns,
    incidents: incidents.map((incident) => ({
      code: incident.code,
      title: incident.title,
      titleAr: incident.titleAr,
      assetCode: incident.asset?.code ?? null,
      regionCode: incident.region?.code ?? null,
      severity: incident.severity,
    })),
    alerts: alerts.map((alert) => ({
      code: alert.code,
      title: alert.title,
      titleAr: alert.titleAr,
      assetCode: alert.asset?.code ?? null,
      level: alert.level,
    })),
    predictions: predictions
      .filter((prediction) => prediction.asset)
      .map((prediction) => ({
        code: prediction.code,
        assetCode: prediction.asset.code,
        eventType: prediction.eventType,
        riskScore: prediction.riskScore,
        dnaCode: prediction.dnaCode,
      })),
  })

  // Asset labels come from the register rather than the snapshot, which carries codes
  // only; doing it here keeps the engine free of a second database read.
  const names = await prisma.asset.findMany({ select: { code: true, name: true, nameAr: true } })
  const nameByCode = new Map(names.map((asset) => [asset.code, asset]))
  for (const node of graph.nodes) {
    if (node.kind !== 'asset') continue
    const record = nameByCode.get(node.refId)
    if (!record) continue
    node.label = record.name
    node.labelAr = record.nameAr
  }

  store.nabdhKnowledgeCache = { value: graph, expiresAt: Date.now() + KNOWLEDGE_TTL_MS }
  return graph
}

export function invalidateKnowledgeCache() {
  store.nabdhKnowledgeCache = undefined
}

export async function getNeighbourhood(
  code: string,
  depth = 1,
): Promise<KnowledgeNeighbourhood> {
  const graph = await loadKnowledgeGraph()
  return neighbourhood(graph, code, depth)
}

export async function search(query: string, limit = 20): Promise<KnowledgeNode[]> {
  const graph = await loadKnowledgeGraph()
  return searchKnowledge(graph, query, limit)
}

/** Persist the graph so `knowledge_nodes` / `knowledge_edges` hold a readable snapshot. */
export async function persistKnowledgeGraph(graph: KnowledgeGraphData): Promise<{ nodes: number; edges: number }> {
  await prisma.knowledgeEdge.deleteMany()
  await prisma.knowledgeNode.deleteMany()

  await prisma.knowledgeNode.createMany({
    data: graph.nodes.map((node) => ({
      code: node.code,
      kind: node.kind,
      label: node.label,
      labelAr: node.labelAr,
      detail: node.detail,
      detailAr: node.detailAr,
      regionCode: node.regionCode,
      weight: node.weight,
      refType: node.refType,
      refId: node.refId,
    })),
  })

  const stored = await prisma.knowledgeNode.findMany({ select: { id: true, code: true } })
  const idByCode = new Map(stored.map((node) => [node.code, node.id]))

  const edges = graph.edges
    .map((edge) => {
      const fromNodeId = idByCode.get(edge.from)
      const toNodeId = idByCode.get(edge.to)
      if (!fromNodeId || !toNodeId) return null
      return {
        fromNodeId,
        toNodeId,
        relation: edge.relation,
        relationAr: edge.relationAr,
        weight: edge.weight,
      }
    })
    .filter((edge): edge is NonNullable<typeof edge> => edge !== null)

  await prisma.knowledgeEdge.createMany({ data: edges })
  return { nodes: graph.nodes.length, edges: edges.length }
}
