import { eq } from "drizzle-orm"
import { db } from "../db/index.js"
import { trafficFlowSettings } from "../db/schema.js"
import type { FlowHostPeer, TrafficFlowSettingsDto, TrafficFlowSettingsPatch } from "@mmapp/contracts/traffic-flow"
import { generateWireGuardKeyPair } from "./wg-keys.js"

function nowIso() {
  return new Date().toISOString()
}

function parsePeers(raw: string): FlowHostPeer[] {
  try {
    const parsed = JSON.parse(raw) as unknown
    if (!Array.isArray(parsed)) return []
    return parsed.filter((p): p is FlowHostPeer =>
      p != null && typeof p === "object" && typeof (p as FlowHostPeer).publicKey === "string",
    )
  } catch {
    return []
  }
}

export function getTrafficFlowSettingsRow() {
  const row = db.select().from(trafficFlowSettings).where(eq(trafficFlowSettings.id, 1)).limit(1).all()[0]
  if (row) return row
  const now = nowIso()
  db.insert(trafficFlowSettings).values({
    id: 1,
    enabled: false,
    collectorIp: "10.255.254.1",
    flowListenPort: 4739,
    wgListenPort: 51821,
    prefix: "10.255.254.0/24",
    createdAt: now,
    updatedAt: now,
  }).run()
  return db.select().from(trafficFlowSettings).where(eq(trafficFlowSettings.id, 1)).limit(1).all()[0]
}

export function toTrafficFlowSettingsDto(
  listener: { bound: boolean; address: string | null },
): TrafficFlowSettingsDto {
  const row = getTrafficFlowSettingsRow()
  return {
    enabled: row.enabled,
    collectorIp: row.collectorIp,
    flowListenPort: row.flowListenPort,
    wgListenPort: row.wgListenPort,
    prefix: row.prefix,
    publicEndpoint: row.publicEndpoint,
    hostPublicKey: row.hostPublicKey,
    hasHostPrivateKey: Boolean(row.hostPrivateKey),
    hubServerId: row.hubServerId ?? null,
    retentionHours: row.retentionHours,
    topN: row.topN,
    mapServiceMinSharePct: Number(row.mapServiceMinSharePct ?? 5),
    lastDatagramAt: row.lastDatagramAt ?? null,
    lastExporterIp: row.lastExporterIp ?? null,
    lastError: row.lastError || null,
    packetsReceived: row.packetsReceived,
    listenerBound: listener.bound,
    listenerAddress: listener.address,
    peers: parsePeers(row.peersJson),
  }
}

export function updateTrafficFlowSettings(patch: TrafficFlowSettingsPatch) {
  const row = getTrafficFlowSettingsRow()
  db.update(trafficFlowSettings).set({
    enabled: patch.enabled ?? row.enabled,
    collectorIp: patch.collectorIp ?? row.collectorIp,
    flowListenPort: patch.flowListenPort ?? row.flowListenPort,
    wgListenPort: patch.wgListenPort ?? row.wgListenPort,
    prefix: patch.prefix ?? row.prefix,
    publicEndpoint: patch.publicEndpoint ?? row.publicEndpoint,
    hubServerId: patch.hubServerId === undefined ? row.hubServerId : patch.hubServerId,
    retentionHours: patch.retentionHours ?? row.retentionHours,
    topN: patch.topN ?? row.topN,
    mapServiceMinSharePct: patch.mapServiceMinSharePct == null
      ? row.mapServiceMinSharePct
      : Math.min(100, Math.max(0, patch.mapServiceMinSharePct)),
    updatedAt: nowIso(),
  }).where(eq(trafficFlowSettings.id, 1)).run()
  return getTrafficFlowSettingsRow()
}

export function ensureHostKeys(): { publicKey: string; created: boolean } {
  const row = getTrafficFlowSettingsRow()
  if (row.hostPublicKey && row.hostPrivateKey) {
    return { publicKey: row.hostPublicKey, created: false }
  }
  const keys = generateWireGuardKeyPair()
  db.update(trafficFlowSettings).set({
    hostPublicKey: keys.publicKey,
    hostPrivateKey: keys.privateKey,
    updatedAt: nowIso(),
  }).where(eq(trafficFlowSettings.id, 1)).run()
  return { publicKey: keys.publicKey, created: true }
}

export function upsertHostPeer(peer: FlowHostPeer) {
  const row = getTrafficFlowSettingsRow()
  const peers = parsePeers(row.peersJson).filter((p) => p.serverId !== peer.serverId)
  peers.push(peer)
  db.update(trafficFlowSettings).set({
    peersJson: JSON.stringify(peers),
    updatedAt: nowIso(),
  }).where(eq(trafficFlowSettings.id, 1)).run()
}

export function recordFlowPacket(exporterIp: string) {
  const row = getTrafficFlowSettingsRow()
  db.update(trafficFlowSettings).set({
    lastDatagramAt: nowIso(),
    lastExporterIp: exporterIp,
    packetsReceived: row.packetsReceived + 1,
    updatedAt: nowIso(),
  }).where(eq(trafficFlowSettings.id, 1)).run()
}

export function recordFlowListenerError(message: string) {
  db.update(trafficFlowSettings).set({
    lastError: message,
    updatedAt: nowIso(),
  }).where(eq(trafficFlowSettings.id, 1)).run()
}

export function enableTrafficFlowIngest() {
  db.update(trafficFlowSettings).set({
    enabled: true,
    updatedAt: nowIso(),
  }).where(eq(trafficFlowSettings.id, 1)).run()
}

export function listHostPeers(): FlowHostPeer[] {
  return parsePeers(getTrafficFlowSettingsRow().peersJson)
}

export function resetFlowIngestCounters(): void {
  db.update(trafficFlowSettings).set({
    packetsReceived: 0,
    lastDatagramAt: null,
    lastExporterIp: null,
    lastError: "",
    updatedAt: nowIso(),
  }).where(eq(trafficFlowSettings.id, 1)).run()
}
