export type FlowPlane = "payload" | "client_mesh" | "overlay" | "mgmt"

export const PLANE_LABEL: Record<FlowPlane, string> = {
  payload: "Интернет",
  client_mesh: "Клиенты",
  overlay: "JH↔EN",
  mgmt: "mgmt",
}

const WG_PORTS = new Set([51820, 13232, 51821])
const FLOW_PORTS = new Set([4739, 2055])

export function isRfc1918(ip: string): boolean {
  const parts = String(ip ?? "").split(".").map((n) => Number.parseInt(n, 10))
  if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n))) return false
  const [a, b] = parts
  if (a === 10) return true
  if (a === 192 && b === 168) return true
  if (a === 172 && b != null && b >= 16 && b <= 31) return true
  if (a === 100 && b != null && b >= 64 && b <= 127) return true
  return false
}

export function isPublicV4(ip: string): boolean {
  const parts = String(ip ?? "").split(".").map((n) => Number.parseInt(n, 10))
  if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n))) return false
  const a = parts[0] ?? 0
  if (a === 0 || a === 127 || a >= 224) return false
  return !isRfc1918(ip)
}

export function isTunnelProto(proto: number, srcPort: number, dstPort: number): boolean {
  if (proto === 47 || proto === 50) return true
  if (proto === 17 && (WG_PORTS.has(srcPort) || WG_PORTS.has(dstPort))) return true
  return false
}

function ifaceLooksMgmt(name: string): boolean {
  const n = name.trim().toLowerCase()
  return n === "wg-flow" || n.endsWith("/wg-flow") || n.includes("wg-flow")
}

export interface PlaneFlowInput {
  src: string
  dst: string
  proto: number
  srcPort: number
  dstPort: number
  inIface: string
  outIface?: string
}

/** Быстрая классификация без топологии — для live ring на ingest. */
export function classifyFlowPlaneLite(flow: PlaneFlowInput): FlowPlane {
  if (ifaceLooksMgmt(flow.inIface)) return "mgmt"
  if (flow.proto === 17 && (FLOW_PORTS.has(flow.srcPort) || FLOW_PORTS.has(flow.dstPort))) return "mgmt"
  if (isTunnelProto(flow.proto, flow.srcPort, flow.dstPort)) return "overlay"
  if (isRfc1918(flow.src) && isRfc1918(flow.dst)) return "client_mesh"
  return "payload"
}

export interface PlaneTopology {
  clientIfaceNames: Set<string>
  enHosts: Set<string>
  jhHosts: Set<string>
}

function hostHit(ip: string, hosts: Set<string>): boolean {
  return Boolean(ip) && hosts.has(ip)
}

export function classifyFlowPlane(
  flow: PlaneFlowInput,
  topo?: PlaneTopology | null,
): FlowPlane {
  const lite = classifyFlowPlaneLite(flow)
  if (!topo) return lite
  if (lite === "mgmt") return "mgmt"
  if (lite === "overlay") return "overlay"
  const srcEn = hostHit(flow.src, topo.enHosts) || hostHit(flow.src, topo.jhHosts)
  const dstEn = hostHit(flow.dst, topo.enHosts) || hostHit(flow.dst, topo.jhHosts)
  if (srcEn && dstEn && isPublicV4(flow.src) && isPublicV4(flow.dst)) return "overlay"
  if (lite === "client_mesh") {
    const inClient = topo.clientIfaceNames.has(flow.inIface)
    const outClient = Boolean(flow.outIface && topo.clientIfaceNames.has(flow.outIface))
    if (inClient || outClient || (isRfc1918(flow.src) && isRfc1918(flow.dst))) return "client_mesh"
  }
  return "payload"
}

export function shouldKeepPlane(
  plane: FlowPlane,
  opts: { excludeMesh?: boolean; excludeOverlay?: boolean },
): boolean {
  if (plane === "mgmt") return false
  if (opts.excludeMesh !== false && plane === "client_mesh") return false
  if (opts.excludeOverlay !== false && plane === "overlay") return false
  return true
}

export function flowBps(bytes: number, startMs: number, endMs: number, windowSec: number): number {
  if (startMs > 0 && endMs > startMs) {
    const sec = Math.max(1, (endMs - startMs) / 1000)
    return (bytes * 8) / sec
  }
  return (bytes * 8) / Math.max(1, windowSec)
}
