import { and, asc, 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"

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

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()
}

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")
        let sumRx = 0
        let sumTx = 0
        for (const i of ifaces) {
          sumRx += toNum(i["rx-bits-per-second"]) / 1_000_000
          sumTx += toNum(i["tx-bits-per-second"]) / 1_000_000
        }
        if (ifaces.length > 0) {
          db.insert(trafficSamples).values(
            ifaces.map((i) => ({
              serverId: srv.id,
              interfaceName: i.name ?? "unknown",
              sampledAt: now,
              rxBytes: toNum(i["rx-byte"]),
              txBytes: toNum(i["tx-byte"]),
              rxBps: toNum(i["rx-bits-per-second"]),
              txBps: toNum(i["tx-bits-per-second"]),
              running: (i.running ?? "false") === "true",
              disabled: (i.disabled ?? "false") === "true",
            })),
          ).run()
        }
        snapshot.servers.push({
          serverId: srv.id,
          name,
          host: srv.host,
          ok: true,
          interfaces: ifaces.length,
          sumRxMbps: Math.round(sumRx),
          sumTxMbps: Math.round(sumTx),
        })
      } 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()
}
