import { eq, or } from "drizzle-orm"
import { db, sqliteDatabase } from "../db/index.js"
import { servers, serversApiPingSettings, serversRestPingSamples } from "../db/schema.js"
import type { ServersRestPingRunSnapshot } from "../types/scheduler-run-snapshot.js"
import { SCHEDULER_RUN_SNAPSHOT_VERSION } from "../types/scheduler-run-snapshot.js"
import { MikrotikClient } from "./mikrotik.js"

let collecting = false

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

/** Как uptime: включённые в каталоге или Home Router (часто «выкл.» при доступе только из LAN). */
function serversForRestProbe() {
  return db
    .select()
    .from(servers)
    .where(or(eq(servers.enabled, true), eq(servers.type, "home-router")))
    .all()
}

export function getServersApiPingSettings() {
  return getSettingsRow()
}

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

export function getServersRestPingCollectorState(): { running: boolean } {
  return { running: collecting }
}

function loadLastRestSampleByServer(): Map<number, { ok: boolean }> {
  const rows = sqliteDatabase
    .prepare(
      `SELECT s.server_id AS serverId, s.ok
       FROM servers_rest_ping_samples s
       INNER JOIN (
         SELECT server_id, MAX(id) AS mid FROM servers_rest_ping_samples GROUP BY server_id
       ) t ON t.mid = s.id`,
    )
    .all() as { serverId: number; ok: number }[]
  const m = new Map<number, { ok: boolean }>()
  for (const r of rows) m.set(r.serverId, { ok: Boolean(r.ok) })
  return m
}

/** Последний сэмпл ресурсов — для согласования «восстановился» с REST без второго тика uptime. */
function loadLatestResourceDownServerIds(): Set<number> {
  const rows = sqliteDatabase
    .prepare(
      `WITH r AS (
         SELECT urs.server_id AS serverId, urs.status,
                ROW_NUMBER() OVER (PARTITION BY urs.server_id ORDER BY urs.id DESC) AS rn
         FROM uptime_resource_samples urs
       )
       SELECT serverId FROM r WHERE rn = 1 AND status IN ('offline', 'degraded')`,
    )
    .all() as { serverId: number }[]
  return new Set(rows.map((x) => x.serverId))
}

/**
 * Лёгкая проверка: GET `/system/identity` (REST) на узел, замер RTT.
 * Пишет строки в `servers_rest_ping_samples` для алерт-движка (см. `buildSignalSnapshot`).
 */
export async function collectServersRestPingOnce(): Promise<ServersRestPingRunSnapshot> {
  const sampledAt = new Date().toISOString()
  if (collecting) {
    return {
      v: SCHEDULER_RUN_SNAPSHOT_VERSION,
      job: "servers_rest_ping",
      sampledAt,
      skipped: true,
      servers: [],
    }
  }
  collecting = true
  const startedAt = Date.now()
  const snapshot: ServersRestPingRunSnapshot = {
    v: SCHEDULER_RUN_SNAPSHOT_VERSION,
    job: "servers_rest_ping",
    sampledAt,
    servers: [],
  }

  try {
    const targets = serversForRestProbe()
    const rows = await Promise.all(
      targets.map(async (srv) => {
        const name = srv.name || srv.host
        const t0 = performance.now()
        try {
          const client = MikrotikClient.fromServer(srv)
          await client.getIdentity()
          const latencyMs = Math.round(performance.now() - t0)
          return {
            serverId: srv.id,
            name,
            host: srv.host,
            ok: true as const,
            latencyMs,
          }
        } catch (err) {
          return {
            serverId: srv.id,
            name,
            host: srv.host,
            ok: false as const,
            latencyMs: null as number | null,
            error: err instanceof Error ? err.message : String(err),
          }
        }
      }),
    )
    snapshot.servers = rows.sort((a, b) => a.serverId - b.serverId)

    const lastByServer = loadLastRestSampleByServer()
    const resourceDown = loadLatestResourceDownServerIds()

    db.transaction((tx) => {
      for (const row of snapshot.servers) {
        const sid = row.serverId
        const last = lastByServer.get(sid)
        const errText = row.ok ? null : (row.error ?? null)

        const insertOne = (ok: boolean, latencyMs: number | null, error: string | null) => {
          tx.insert(serversRestPingSamples).values({
            serverId: sid,
            sampledAt,
            ok,
            latencyMs,
            error,
          }).run()
        }

        const lat = row.latencyMs ?? null

        if (row.ok) {
          const downRes = resourceDown.has(sid)
          if (last === undefined && downRes) {
            insertOne(false, null, null)
          }
          insertOne(true, lat, null)
          if (last?.ok === false || (last === undefined && downRes)) {
            insertOne(true, lat, null)
          }
        } else {
          insertOne(false, null, errText)
        }
      }
    })

    sqliteDatabase
      .prepare(`DELETE FROM servers_rest_ping_samples WHERE sampled_at < datetime('now', '-30 days')`)
      .run()

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