import { desc, eq } from "drizzle-orm"
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
import { db } from "../db/index.js"
import { serverSnapshots, servers } from "../db/schema.js"
import {
  collectTrafficOnce,
  getTrafficCollectorState,
  getTrafficSettings,
  readServerSamplesInRange,
  updateTrafficSettings,
} from "../services/traffic-collector.js"
import { scheduleAlertEngineAfterDataCollectors } from "../services/alert-collector-hooks.js"
import { refreshScheduler } from "../services/scheduler.js"
import { appendEvent } from "../modules/events/service/events-service.js"

type SnapshotRow = typeof serverSnapshots.$inferSelect

interface TrafficServerDto {
  id: string
  name: string
  site: string
  country: string
  status: "online" | "offline" | "degraded"
  rxNow: number
  txNow: number
  rxPeak: number
  txPeak: number
  rxTotal: number
  txTotal: number
  sessions: number
  rxSeries: number[]
  txSeries: number[]
}

interface TrafficInterfaceDto {
  name: string
  running: boolean
  disabled: boolean
  rxNow: number
  txNow: number
}

function latestSnapshot(serverId: number): SnapshotRow | undefined {
  return db
    .select()
    .from(serverSnapshots)
    .where(eq(serverSnapshots.serverId, serverId))
    .orderBy(desc(serverSnapshots.polledAt))
    .limit(1)
    .all()[0]
}

function rangeToMinutes(range: string | undefined): number {
  switch ((range ?? "1h").toLowerCase()) {
    case "5m": return 5
    case "15m": return 15
    case "1h": return 60
    case "4h": return 240
    case "24h": return 1440
    default: return 60
  }
}

function toSeries(values: number[], target = 60): number[] {
  if (values.length === 0) return Array(target).fill(0)
  if (values.length === target) return values
  if (values.length > target) return values.slice(values.length - target)
  const head = Array(target - values.length).fill(values[0] ?? 0)
  return [...head, ...values]
}

function buildServerTraffic(
  s: typeof servers.$inferSelect,
  status: TrafficServerDto["status"],
  rows: Array<{
    interfaceName: string
    sampledAt: string
    rxBps: number
    txBps: number
    rxBytes: number
    txBytes: number
    running: boolean
    disabled: boolean
  }>,
  onlyInterface?: string,
): TrafficServerDto {
  const filteredRows = onlyInterface
    ? rows.filter((r) => r.interfaceName === onlyInterface)
    : rows

  if (filteredRows.length === 0) {
    return {
      id: String(s.id),
      name: s.name || s.host,
      site: s.site || "—",
      country: s.country || "UN",
      status,
      rxNow: 0,
      txNow: 0,
      rxPeak: 0,
      txPeak: 0,
      rxTotal: 0,
      txTotal: 0,
      sessions: 0,
      rxSeries: Array(60).fill(0),
      txSeries: Array(60).fill(0),
    }
  }

  const bySampleTs = new Map<string, { rx: number; tx: number }>()
  const byIface = new Map<string, typeof filteredRows>()
  for (const r of filteredRows) {
    const ts = r.sampledAt
    const cur = bySampleTs.get(ts) ?? { rx: 0, tx: 0 }
    cur.rx += Math.max(0, r.rxBps) / 1_000_000
    cur.tx += Math.max(0, r.txBps) / 1_000_000
    bySampleTs.set(ts, cur)
    const arr = byIface.get(r.interfaceName) ?? []
    arr.push(r)
    byIface.set(r.interfaceName, arr)
  }

  const seriesPoints = [...bySampleTs.entries()]
    .sort((a, b) => a[0].localeCompare(b[0]))
    .map(([, v]) => ({ rx: Math.round(v.rx), tx: Math.round(v.tx) }))
  const rxSeries = toSeries(seriesPoints.map((p) => p.rx))
  const txSeries = toSeries(seriesPoints.map((p) => p.tx))
  const rxNow = rxSeries[rxSeries.length - 1] ?? 0
  const txNow = txSeries[txSeries.length - 1] ?? 0
  const rxPeak = rxSeries.reduce((m, v) => Math.max(m, v), 0)
  const txPeak = txSeries.reduce((m, v) => Math.max(m, v), 0)

  let rxBytesDelta = 0
  let txBytesDelta = 0
  let sessions = 0
  for (const arr of byIface.values()) {
    const sorted = [...arr].sort((a, b) => a.sampledAt.localeCompare(b.sampledAt))
    const first = sorted[0]
    const last = sorted[sorted.length - 1]
    if (first && last) {
      const dRx = last.rxBytes - first.rxBytes
      const dTx = last.txBytes - first.txBytes
      rxBytesDelta += dRx >= 0 ? dRx : last.rxBytes
      txBytesDelta += dTx >= 0 ? dTx : last.txBytes
      if (last.running && !last.disabled) sessions += 1
    }
  }

  return {
    id: String(s.id),
    name: s.name || s.host,
    site: s.site || "—",
    country: s.country || "UN",
    status,
    rxNow,
    txNow,
    rxPeak,
    txPeak,
    rxTotal: Number((rxBytesDelta / (1024 ** 3)).toFixed(1)),
    txTotal: Number((txBytesDelta / (1024 ** 3)).toFixed(1)),
    sessions,
    rxSeries,
    txSeries,
  }
}

const trafficRoutes: FastifyPluginAsyncZod = async (app) => {
  app.get("/traffic/settings", async (_req, reply) => {
    const settings = getTrafficSettings()
    const state = getTrafficCollectorState()
    return reply.send({
      enabled: settings.enabled,
      intervalSec: settings.intervalSec,
      retentionDays: settings.retentionDays,
      lastCollectedAt: settings.lastCollectedAt ?? null,
      lastDurationMs: settings.lastDurationMs ?? null,
      lastError: settings.lastError || null,
      collectorRunning: state.running,
    })
  })

  app.put("/traffic/settings", async (req, reply) => {
    const body = req.body as {
      enabled?: boolean
      intervalSec?: number | string
      retentionDays?: number | string
    }
    const intervalSec = body.intervalSec == null ? undefined : Math.max(5, Number.parseInt(String(body.intervalSec), 10) || 30)
    const retentionDays = body.retentionDays == null ? undefined : Math.max(1, Number.parseInt(String(body.retentionDays), 10) || 14)
    const updated = updateTrafficSettings({
      enabled: body.enabled,
      intervalSec,
      retentionDays,
    })
    refreshScheduler()
    return reply.send({
      ok: true,
      settings: {
        enabled: updated.enabled,
        intervalSec: updated.intervalSec,
        retentionDays: updated.retentionDays,
        lastCollectedAt: updated.lastCollectedAt ?? null,
        lastDurationMs: updated.lastDurationMs ?? null,
        lastError: updated.lastError || null,
      },
    })
  })

  app.post("/traffic/collect-now", async (_req, reply) => {
    try {
      await collectTrafficOnce()
      scheduleAlertEngineAfterDataCollectors()
      const updated = getTrafficSettings()
      appendEvent({
        level: "info",
        eventType: "traffic.collect.manual.ok",
        sourceModule: "traffic",
        title: "Ручной сбор трафика завершен",
        message: `Длительность: ${updated.lastDurationMs ?? 0} мс`,
        payload: {
          lastCollectedAt: updated.lastCollectedAt ?? null,
        },
      })
      return reply.send({
        ok: true,
        lastCollectedAt: updated.lastCollectedAt ?? null,
        lastDurationMs: updated.lastDurationMs ?? null,
        lastError: updated.lastError || null,
      })
    } catch (error) {
      const msg = error instanceof Error ? error.message : String(error)
      appendEvent({
        level: "critical",
        eventType: "traffic.collect.manual.failed",
        sourceModule: "traffic",
        title: "Ошибка ручного сбора трафика",
        message: msg,
      })
      return reply.status(500).send({ error: msg })
    }
  })

  app.get("/traffic/servers", async (req, reply) => {
    const q = req.query as { range?: string }
    const minutes = rangeToMinutes(q.range)
    const sinceIso = new Date(Date.now() - minutes * 60_000).toISOString()
    const allServers = db.select().from(servers).where(eq(servers.enabled, true)).all()
    const data = allServers.map((s): TrafficServerDto => {
      const snap = latestSnapshot(s.id)
      const status: TrafficServerDto["status"] =
        snap?.status === "offline" ? "offline" : (snap?.status === "online" ? "online" : "degraded")

      const rows = readServerSamplesInRange(s.id, sinceIso)
      return buildServerTraffic(s, status, rows)
    })

    return reply.send({ servers: data })
  })

  app.get("/traffic/servers/:id/interfaces", async (req, reply) => {
    const p = req.params as { id?: string | number }
    const serverId = Number.parseInt(String(p.id ?? ""), 10)
    if (!Number.isFinite(serverId)) return reply.status(400).send({ error: "id is required" })

    const allRows = db.select().from(servers).where(eq(servers.id, serverId)).limit(1).all()
    const server = allRows[0]
    if (!server) return reply.status(404).send({ error: "Server not found" })

    const sinceIso = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()
    const rows = readServerSamplesInRange(serverId, sinceIso)
    const byIface = new Map<string, typeof rows>()
    for (const r of rows) {
      const arr = byIface.get(r.interfaceName) ?? []
      arr.push(r)
      byIface.set(r.interfaceName, arr)
    }
    const interfaces: TrafficInterfaceDto[] = [...byIface.entries()].map(([name, arr]) => {
      const sorted = arr.sort((a, b) => a.sampledAt.localeCompare(b.sampledAt))
      const last = sorted[sorted.length - 1]
      return {
        name,
        running: Boolean(last?.running),
        disabled: Boolean(last?.disabled),
        rxNow: Math.round((last?.rxBps ?? 0) / 1_000_000),
        txNow: Math.round((last?.txBps ?? 0) / 1_000_000),
      }
    }).sort((a, b) => (b.rxNow + b.txNow) - (a.rxNow + a.txNow))

    return reply.send({ interfaces })
  })

  app.get("/traffic/servers/:id", async (req, reply) => {
    const p = req.params as { id?: string | number }
    const q = req.query as { range?: string; iface?: string }
    const serverId = Number.parseInt(String(p.id ?? ""), 10)
    if (!Number.isFinite(serverId)) return reply.status(400).send({ error: "id is required" })
    const server = db.select().from(servers).where(eq(servers.id, serverId)).limit(1).all()[0]
    if (!server) return reply.status(404).send({ error: "Server not found" })

    const minutes = rangeToMinutes(q.range)
    const sinceIso = new Date(Date.now() - minutes * 60_000).toISOString()
    const rows = readServerSamplesInRange(server.id, sinceIso)
    const snap = latestSnapshot(server.id)
    const status: TrafficServerDto["status"] =
      snap?.status === "offline" ? "offline" : (snap?.status === "online" ? "online" : "degraded")
    const data = buildServerTraffic(server, status, rows, q.iface && q.iface !== "__all__" ? q.iface : undefined)
    return reply.send({ server: data })
  })
}

export default trafficRoutes

