import { lookupBrand, OTHER_SERVICE } from "./traffic-flow-brands.js"
import { db } from "../db/index.js"
import { evobgpSettings } from "../db/schema.js"
import { ipInCidrV4, parseCidrV4 } from "./traffic-flow-ip.js"
import type { FlowIpMeta } from "./traffic-flow-ripe.js"
import { applicationName } from "./traffic-flow-apps.js"

export interface FlowClassification {
  service: string
  category: string
}

interface CatalogCidr {
  cidr: string
  purpose: string
  prefixLen: number
}

const CATALOG_TTL_MS = 10 * 60_000
let cidrs: CatalogCidr[] = []
let asnPurpose = new Map<number, string>()
let fetchedAt = 0
let catalogFetchEnabled = true
let inflight: Promise<void> | null = null

export function disableCatalogFetchForTests(): void {
  catalogFetchEnabled = false
}

export function resetFlowCatalogForTests(): void {
  cidrs = []
  asnPurpose = new Map()
  fetchedAt = 0
  inflight = null
}

export function seedFlowCatalogForTests(input: {
  cidrs?: Array<{ cidr: string; purpose: string }>
  asns?: Array<{ asn: number; purpose: string }>
}): void {
  cidrs = (input.cidrs ?? [])
    .map((c) => ({ cidr: c.cidr, purpose: c.purpose, prefixLen: parseCidrV4(c.cidr)?.prefixLen ?? 0 }))
    .sort((a, b) => b.prefixLen - a.prefixLen)
  asnPurpose = new Map((input.asns ?? []).map((a) => [a.asn, a.purpose]))
  fetchedAt = Date.now()
}

export function categoryFromPurpose(purpose: string, proto: number, dstPort: number, srcPort: number): string {
  const p = purpose.toLowerCase()
  if (/gaming|steam|epic|riot/.test(p)) return "Игры"
  if (/streaming|youtube|netflix|twitch|video/.test(p)) return "Видео / стриминг"
  if (/cdn|cloudflare|akamai|fastly/.test(p)) return "CDN"
  if (/voip|discord|zoom/.test(p)) return "Голос"
  if (/openai|chatgpt|\bai\b/.test(p)) return "ИИ"
  if (/веб|web|google/.test(p)) return "Веб"
  const app = applicationName(proto, dstPort, srcPort)
  if (app === "DNS" || app === "SSH" || app === "BGP") return app
  if (app === "GRE" || app === "ESP" || app === "WireGuard") return "Туннель"
  return OTHER_SERVICE
}

function matchCidr(ip: string): CatalogCidr | null {
  for (const row of cidrs) {
    if (ipInCidrV4(ip, row.cidr)) return row
  }
  return null
}

export function classifyFlowDst(
  dst: string,
  proto: number,
  dstPort: number,
  srcPort: number,
  ripe: FlowIpMeta | null,
): FlowClassification {
  if (proto === 47) return { service: "GRE", category: "Туннель" }
  if (proto === 50) return { service: "ESP", category: "Туннель" }
  const app = applicationName(proto, dstPort, srcPort)
  if (app === "WireGuard") return { service: "WireGuard", category: "Туннель" }
  const hit = matchCidr(dst)
  const holder = ripe?.holder ?? ""
  const youtubeHolder = /youtube/i.test(holder)
  const brand = youtubeHolder
    ? { service: "YouTube", category: "Видео / стриминг" }
    : lookupBrand(dst, ripe?.asn ?? 0)
  const asnName = ripe?.asn ? asnPurpose.get(ripe.asn) : undefined
  const service = (hit?.purpose || brand?.service || asnName || OTHER_SERVICE).trim() || OTHER_SERVICE
  const category = hit
    ? categoryFromPurpose(hit.purpose, proto, dstPort, srcPort)
    : (brand?.category || categoryFromPurpose(asnName || "", proto, dstPort, srcPort))
  return { service, category }
}

async function fetchCatalog(): Promise<void> {
  if (!catalogFetchEnabled) return
  if (Date.now() - fetchedAt < CATALOG_TTL_MS) return
  if (inflight) return inflight
  inflight = (async () => {
    try {
      const row = db.select().from(evobgpSettings).limit(1).all()[0]
      if (!row?.enabled) return
      const root = String(row.baseUrl ?? "").replace(/\/+$/, "")
      const token = String(row.apiKey ?? "").replace(/^Bearer\s+/i, "").trim()
      if (!root || !token) return
      const ac = new AbortController()
      const t = setTimeout(() => ac.abort(), 20_000)
      try {
        const res = await fetch(`${root}/v1/router-lists/catalog`, {
          headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
          signal: ac.signal,
        })
        if (!res.ok) return
        const catalog = await res.json() as {
          modules?: { items?: Array<{ id: string; name: string }> }
          ip_ranges?: { items?: Array<{ module_id: string; entry: { prefix: string } }> }
          asns?: { items?: Array<{ module_id: string; entry: { asn: number } }> }
        }
        const mods = new Map((catalog.modules?.items ?? []).map((m) => [m.id, m.name]))
        const next: CatalogCidr[] = []
        for (const item of catalog.ip_ranges?.items ?? []) {
          const prefix = String(item.entry?.prefix ?? "").trim()
          const purpose = mods.get(item.module_id) ?? ""
          const parsed = parseCidrV4(prefix)
          if (!prefix || !parsed) continue
          next.push({ cidr: prefix, purpose, prefixLen: parsed.prefixLen })
        }
        next.sort((a, b) => b.prefixLen - a.prefixLen)
        const nextAsn = new Map<number, string>()
        for (const item of catalog.asns?.items ?? []) {
          const purpose = mods.get(item.module_id)
          const asn = Number(item.entry?.asn)
          if (purpose && Number.isFinite(asn) && asn > 0) nextAsn.set(asn, purpose)
        }
        cidrs = next
        asnPurpose = nextAsn
        fetchedAt = Date.now()
      } finally {
        clearTimeout(t)
      }
    } catch {
      /* catalog optional */
    } finally {
      inflight = null
    }
  })()
  return inflight
}

/** Background refresh — analytics never awaits the HTTP. */
export function refreshFlowCatalogInBackground(): void {
  void fetchCatalog()
}
