import { and, asc, desc, eq, gte, lt } from "drizzle-orm"
import { db } from "../db/index.js"
import { servers, trafficSamples, trafficSettings } from "../db/schema.js"
import type { TrafficRunSnapshot } from "../types/scheduler-run-snapshot.js"
import { SCHEDULER_RUN_SNAPSHOT_VERSION } from "../types/scheduler-run-snapshot.js"
import { MikrotikClient } from "./mikrotik.js"
import { bpsToMbps, rateBpsFromDelta, shouldIncludeIface } from "./traffic-rate.js"
import { rememberServerIfaces } from "./traffic-flow-ifindex.js"

interface RosIfaceTraffic {
  ".id"?: string
  name?: string
  ifindex?: string
  running?: string
  disabled?: string
  "rx-byte"?: string
  "tx-byte"?: string
  "rx-bits-per-second"?: string
  "tx-bits-per-second"?: string
}

interface RosWgPeerTraffic {
  interface?: string
  name?: string
  comment?: string
  "public-key"?: string
  rx?: string
  tx?: string
  disabled?: string
}

function waveKey(interfaceName: string, peerPublicKey = ""): string {
  return `${interfaceName}\0${peerPublicKey}`
}

function sampleRate(
  prevWave: Map<string, { rxBytes: number; txBytes: number; sampledAt: string }>,
  key: string,
  rxBytes: number,
  txBytes: number,
  nowMs: number,
): { rxBps: number; txBps: number } {
  const prev = prevWave.get(key)
  const prevMs = prev ? Date.parse(prev.sampledAt) : NaN
  const rxBps = prev && Number.isFinite(prevMs)
    ? (rateBpsFromDelta(prev.rxBytes, rxBytes, prevMs, nowMs) ?? 0)
    : 0
  const txBps = prev && Number.isFinite(prevMs)
    ? (rateBpsFromDelta(prev.txBytes, txBytes, prevMs, nowMs) ?? 0)
    : 0
  return { rxBps, txBps }
}

export interface TrafficCollectorState {
  running: boolean
  lastRunAt: string | null
  lastError: string | null
}

let collecting = false

function toNum(raw: unknown): number {
  const n = Number.parseFloat(String(raw ?? "0"))
  return Number.isFinite(n) ? Math.max(0, Math.round(n)) : 0
}

function getSettingsRow() {
  const row = db.select().from(trafficSettings).where(eq(trafficSettings.id, 1)).limit(1).all()[0]
  if (row) return row
  const now = new Date().toISOString()
  db.insert(trafficSettings).values({
    id: 1,
    enabled: true,
    intervalSec: 30,
    retentionDays: 14,
    createdAt: now,
    updatedAt: now,
  }).run()
  return db.select().from(trafficSettings).where(eq(trafficSettings.id, 1)).limit(1).all()[0]
}

function cleanupOldSamples(retentionDays: number) {
  const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString()
  db.delete(trafficSamples)
    .where(lt(trafficSamples.sampledAt, cutoff))
    .run()
}

function readPreviousWave(serverId: number): Map<string, { rxBytes: number; txBytes: number; sampledAt: string }> {
  const last = db
    .select({ sampledAt: trafficSamples.sampledAt })
    .from(trafficSamples)
    .where(eq(trafficSamples.serverId, serverId))
    .orderBy(desc(trafficSamples.sampledAt))
    .limit(1)
    .all()[0]
  if (!last) return new Map()
  const rows = db
    .select({
      interfaceName: trafficSamples.interfaceName,
      peerPublicKey: trafficSamples.peerPublicKey,
      rxBytes: trafficSamples.rxBytes,
      txBytes: trafficSamples.txBytes,
      sampledAt: trafficSamples.sampledAt,
    })
    .from(trafficSamples)
    .where(and(
      eq(trafficSamples.serverId, serverId),
      eq(trafficSamples.sampledAt, last.sampledAt),
    ))
    .all()
  return new Map(rows.map((r) => [`${r.interfaceName}\0${r.peerPublicKey ?? ""}`, r]))
}

export async function collectTrafficOnce(): Promise<TrafficRunSnapshot> {
  const sampledAt = new Date().toISOString()
  if (collecting) {
    return {
      v: SCHEDULER_RUN_SNAPSHOT_VERSION,
      job: "traffic",
      sampledAt,
      skipped: true,
      servers: [],
    }
  }
  collecting = true
  const startedAt = Date.now()
  const now = sampledAt
  const settings = getSettingsRow()
  const snapshot: TrafficRunSnapshot = {
    v: SCHEDULER_RUN_SNAPSHOT_VERSION,
    job: "traffic",
    sampledAt: now,
    servers: [],
  }

  try {
    const enabledServers = db.select().from(servers).where(eq(servers.enabled, true)).all()

    for (const srv of enabledServers) {
      const name = srv.name || srv.host
      try {
        const client = MikrotikClient.fromServer(srv)
        const ifaces = await client.get<RosIfaceTraffic[]>("/interface")
        rememberServerIfaces(srv.id, ifaces)
        const prevWave = readPreviousWave(srv.id)
        const nowMs = Date.parse(now)
        let sumRxMbps = 0
        let sumTxMbps = 0
        const rows = ifaces.map((i) => {
          const interfaceName = i.name ?? "unknown"
          const rxBytes = toNum(i["rx-byte"])
          const txBytes = toNum(i["tx-byte"])
          const running = (i.running ?? "false") === "true"
          const disabled = (i.disabled ?? "false") === "true"
          const { rxBps, txBps } = sampleRate(prevWave, waveKey(interfaceName), rxBytes, txBytes, nowMs)
          if (shouldIncludeIface(interfaceName, running, disabled)) {
            sumRxMbps += bpsToMbps(rxBps)
            sumTxMbps += bpsToMbps(txBps)
          }
          return {
            serverId: srv.id,
            interfaceName,
            peerPublicKey: "",
            sampledAt: now,
            rxBytes,
            txBytes,
            rxBps,
            txBps,
            running,
            disabled,
          }
        })
        try {
          const peers = await client.get<RosWgPeerTraffic[]>("/interface/wireguard/peers")
          for (const p of peers) {
            const interfaceName = (p.interface ?? "").trim()
            const peerPublicKey = (p["public-key"] ?? "").trim()
            if (!interfaceName || !peerPublicKey) continue
            const rxBytes = toNum(p.rx)
            const txBytes = toNum(p.tx)
            const disabled = (p.disabled ?? "false") === "true" || p.disabled === "yes"
            const running = !disabled
            const { rxBps, txBps } = sampleRate(
              prevWave,
              waveKey(interfaceName, peerPublicKey),
              rxBytes,
              txBytes,
              nowMs,
            )
            rows.push({
              serverId: srv.id,
              interfaceName,
              peerPublicKey,
              sampledAt: now,
              rxBytes,
              txBytes,
              rxBps,
              txBps,
              running,
              disabled,
            })
          }
        } catch {
          /* WG peers optional — iface samples already recorded */
        }
        if (rows.length > 0) {
          db.insert(trafficSamples).values(rows).run()
        }
        snapshot.servers.push({
          serverId: srv.id,
          name,
          host: srv.host,
          ok: true,
          interfaces: ifaces.length,
          sumRxMbps: Math.round(sumRxMbps * 1000) / 1000,
          sumTxMbps: Math.round(sumTxMbps * 1000) / 1000,
        })
      } catch (err) {
        snapshot.servers.push({
          serverId: srv.id,
          name,
          host: srv.host,
          ok: false,
          error: err instanceof Error ? err.message : String(err),
        })
      }
    }

    cleanupOldSamples(Math.max(1, settings.retentionDays))
    db.update(trafficSettings).set({
      lastCollectedAt: now,
      lastDurationMs: Date.now() - startedAt,
      lastError: "",
      updatedAt: now,
    }).where(eq(trafficSettings.id, 1)).run()
  } catch (e) {
    const msg = e instanceof Error ? e.message : String(e)
    snapshot.fatalError = msg
    db.update(trafficSettings).set({
      lastCollectedAt: now,
      lastDurationMs: Date.now() - startedAt,
      lastError: msg,
      updatedAt: now,
    }).where(eq(trafficSettings.id, 1)).run()
  } finally {
    collecting = false
  }
  return snapshot
}

/** Таймеры трафика ведёт планировщик. */
export function stopTrafficCollector() {}

export function restartTrafficCollector() {}

export function getTrafficCollectorState(): TrafficCollectorState {
  const s = getSettingsRow()
  return {
    running: Boolean(s.enabled),
    lastRunAt: s.lastCollectedAt ?? null,
    lastError: s.lastError || null,
  }
}

/** Идёт активный прогон `collectTrafficOnce` (запись в SQLite ещё не завершена). */
export function isTrafficCollecting(): boolean {
  return collecting
}

export function getTrafficSettings() {
  return getSettingsRow()
}

export function updateTrafficSettings(patch: {
  enabled?: boolean
  intervalSec?: number
  retentionDays?: number
}) {
  const prev = getSettingsRow()
  const next = {
    enabled: patch.enabled ?? prev.enabled,
    intervalSec: patch.intervalSec ?? prev.intervalSec,
    retentionDays: patch.retentionDays ?? prev.retentionDays,
    updatedAt: new Date().toISOString(),
  }
  db.update(trafficSettings).set(next).where(eq(trafficSettings.id, 1)).run()
  return getSettingsRow()
}

export function readServerSamplesInRange(serverId: number, sinceIso: string) {
  return db.select()
    .from(trafficSamples)
    .where(and(
      eq(trafficSamples.serverId, serverId),
      gte(trafficSamples.sampledAt, sinceIso),
    ))
    .orderBy(asc(trafficSamples.sampledAt))
    .all()
}
