import { eq } from "drizzle-orm"
import { db } from "../db/index.js"
import { servers } from "../db/schema.js"
import { MikrotikClient } from "./mikrotik.js"
import type { WgIfaceDto, WgPeerDto } from "@mmapp/contracts/wireguard"

type ServerRow = typeof servers.$inferSelect

interface RosWireGuard {
  ".id"?: string
  name?: string
  "listen-port"?: string
  mtu?: string
  "public-key"?: string
  "private-key"?: string
  running?: string
  disabled?: string
  comment?: string
}

interface RosWireGuardPeer {
  ".id"?: string
  interface?: string
  name?: string
  "public-key"?: string
  "endpoint-address"?: string
  "endpoint-port"?: string
  "allowed-address"?: string
  "last-handshake"?: string
  rx?: string
  tx?: string
  disabled?: string
  comment?: string
  "persistent-keepalive"?: string
  "client-address"?: string
  "client-dns"?: string
  "client-endpoint"?: string
}

interface RosIpAddress {
  ".id"?: string
  address?: string
  interface?: string
  disabled?: string
}

function parseBytes(v: string | undefined): number | undefined {
  if (v == null || v === "") return undefined
  const n = Number.parseInt(v, 10)
  return Number.isFinite(n) ? n : undefined
}

function mapPeer(p: RosWireGuardPeer, idx: number): WgPeerDto {
  const rosId = String(p[".id"] ?? `peer-${idx}`)
  const allowed = (p["allowed-address"] ?? "")
    .split(",")
    .map((s) => s.trim())
    .filter(Boolean)
  const epAddr = (p["endpoint-address"] ?? "").trim()
  const epPort = (p["endpoint-port"] ?? "").trim()
  const endpoint = epAddr ? (epPort ? `${epAddr}:${epPort}` : epAddr) : undefined
  const ka = p["persistent-keepalive"]
    ? Number.parseInt(p["persistent-keepalive"], 10)
    : undefined
  return {
    id: rosId,
    rosId,
    publicKey: p["public-key"] ?? "",
    allowedIps: allowed,
    endpoint,
    latestHandshake: p["last-handshake"]?.trim() || undefined,
    transferRx: parseBytes(p.rx),
    transferTx: parseBytes(p.tx),
    persistentKeepalive: Number.isFinite(ka) ? ka : undefined,
    persistent: Number.isFinite(ka) && (ka as number) > 0,
    comment: p.comment ?? undefined,
    disabled: p.disabled === "true" || p.disabled === "yes",
    name: p.name,
    clientAddress: p["client-address"],
    clientDns: p["client-dns"],
    clientEndpoint: p["client-endpoint"],
  }
}

function mapIface(
  server: ServerRow,
  w: RosWireGuard,
  peers: WgPeerDto[],
  address: string | undefined,
  includePrivateKey: boolean,
): WgIfaceDto {
  const rosId = String(w[".id"] ?? w.name ?? "wg")
  const name = (w.name ?? "").trim() || rosId
  const disabled = w.disabled === "true" || w.disabled === "yes"
  const running = w.running === "true" || w.running === "yes"
  return {
    id: `${server.id}:${rosId}`,
    rosId,
    name,
    serverId: String(server.id),
    serverName: String(server.name ?? "").trim() || String(server.host ?? server.id),
    serverCountry: server.country ?? undefined,
    listenPort: Number.parseInt(w["listen-port"] ?? "13231", 10) || 13231,
    mtu: Number.parseInt(w.mtu ?? "1420", 10) || 1420,
    publicKey: w["public-key"] || undefined,
    privateKey: includePrivateKey ? w["private-key"] || undefined : undefined,
    address,
    peers,
    comment: w.comment ?? "",
    enabled: !disabled,
    status: disabled ? "down" : running ? "up" : "down",
  }
}

async function fetchForServer(
  server: ServerRow,
  includePrivateKey: boolean,
): Promise<WgIfaceDto[]> {
  const client = MikrotikClient.fromServer(server)
  const [ifacesRaw, peersRaw, addrsRaw] = await Promise.all([
    client.get<RosWireGuard[]>("/interface/wireguard"),
    client.get<RosWireGuardPeer[]>("/interface/wireguard/peers"),
    client.get<RosIpAddress[]>("/ip/address").catch(() => [] as RosIpAddress[]),
  ])

  const peersByIface = new Map<string, WgPeerDto[]>()
  peersRaw.forEach((p, idx) => {
    const ifaceName = (p.interface ?? "").trim()
    if (!ifaceName) return
    const list = peersByIface.get(ifaceName) ?? []
    list.push(mapPeer(p, idx))
    peersByIface.set(ifaceName, list)
  })

  const addrByIface = new Map<string, string>()
  for (const a of addrsRaw) {
    if (a.disabled === "true" || a.disabled === "yes") continue
    const iface = (a.interface ?? "").trim()
    const addr = (a.address ?? "").trim()
    if (iface && addr && !addrByIface.has(iface)) addrByIface.set(iface, addr)
  }

  return ifacesRaw.map((w) => {
    const name = (w.name ?? "").trim()
    return mapIface(
      server,
      w,
      peersByIface.get(name) ?? [],
      addrByIface.get(name),
      includePrivateKey,
    )
  })
}

export type WgListResult = {
  interfaces: WgIfaceDto[]
  failures: Array<{ serverId: string; serverName?: string; error: string }>
}

export async function listWireGuardInterfaces(opts?: {
  serverId?: string
  includePrivateKey?: boolean
}): Promise<WgListResult> {
  const includePrivateKey = opts?.includePrivateKey === true
  let serverRows: ServerRow[]
  if (opts?.serverId) {
    const id = Number.parseInt(String(opts.serverId), 10)
    if (!Number.isFinite(id)) {
      return { interfaces: [], failures: [{ serverId: String(opts.serverId), error: "Некорректный serverId" }] }
    }
    const row = db.select().from(servers).where(eq(servers.id, id)).limit(1).all()[0]
    serverRows = row ? [row] : []
  } else {
    serverRows = db.select().from(servers).where(eq(servers.enabled, true)).all()
  }

  const failures: WgListResult["failures"] = []
  const results = await Promise.all(
    serverRows.map(async (server) => {
      try {
        return await fetchForServer(server, includePrivateKey)
      } catch (e) {
        failures.push({
          serverId: String(server.id),
          serverName: server.name ?? undefined,
          error: e instanceof Error ? e.message : String(e),
        })
        return [] as WgIfaceDto[]
      }
    }),
  )
  return { interfaces: results.flat(), failures }
}

export async function countWireGuardInterfaces(): Promise<number> {
  try {
    const result = await Promise.race([
      listWireGuardInterfaces({ includePrivateKey: false }),
      new Promise<null>((resolve) => setTimeout(() => resolve(null), 8_000)),
    ])
    if (!result) return 0
    return result.interfaces.length
  } catch {
    return 0
  }
}

export function getEnabledServerById(serverId: string | number): ServerRow | null {
  const id = typeof serverId === "number" ? serverId : Number.parseInt(String(serverId), 10)
  if (!Number.isFinite(id)) return null
  return db.select().from(servers).where(eq(servers.id, id)).limit(1).all()[0] ?? null
}

export type CatalogWgPeer = {
  interfaceName: string
  publicKey: string
  name: string
  comment: string
  allowedIps: string[]
  latestHandshake?: string
  disabled: boolean
}

const WG_CATALOG_TIMEOUT_MS = 5_000

export async function listWireGuardPeersForCatalog(serverId: number): Promise<{
  peers: CatalogWgPeer[]
  error?: string
}> {
  const row = getEnabledServerById(serverId)
  if (!row) return { peers: [], error: "Сервер не найден" }
  try {
    const client = MikrotikClient.fromServer(row)
    const peersRaw = await Promise.race([
      client.get<RosWireGuardPeer[]>("/interface/wireguard/peers"),
      new Promise<never>((_, reject) => {
        setTimeout(() => reject(new Error("Таймаут RouterOS")), WG_CATALOG_TIMEOUT_MS)
      }),
    ])
    const peers: CatalogWgPeer[] = peersRaw.flatMap((p, idx) => {
      const mapped = mapPeer(p, idx)
      const interfaceName = (p.interface ?? "").trim()
      const publicKey = mapped.publicKey.trim()
      if (!interfaceName || !publicKey) return []
      return [{
        interfaceName,
        publicKey,
        name: mapped.name ?? "",
        comment: mapped.comment ?? "",
        allowedIps: mapped.allowedIps,
        latestHandshake: mapped.latestHandshake,
        disabled: mapped.disabled === true,
      }]
    })
    return { peers }
  } catch (e) {
    return { peers: [], error: e instanceof Error ? e.message : String(e) }
  }
}

export { type RosWireGuard, type RosWireGuardPeer }
