/**
 * Данные для страницы «Оптимизатор маршрутов» и единый набор параметров Route AI
 * (см. блок `#route-ai` в `/settings` и страницу `/route-optimizer`).
 */

import { servers, type FilterRule } from "@/lib/data"

// ── Совместимо с app/(main)/route-optimizer/page.tsx ─────────────────────────

export interface WanUplink {
  id: string
  name: string
  isp: string
  iface: string
  ip: string
  maxDl: number
  maxUl: number
}

export interface HomeRouter {
  id: string
  label: string
  site: string
  country: string
  model: string
  ip: string
  wans: WanUplink[]
}

export interface JumpHost {
  id: string
  label: string
  site: string
  country: string
  ip: string
}

export interface ExitNode {
  id: string
  label: string
  site: string
  country: string
  ip: string
}

export interface WanJhLeg {
  wanId: string
  jhId: string
  pingMs: number
  dlMbps: number
  ulMbps: number
  score: number
  loss: number
}

export interface JhExLeg {
  jhId: string
  exitId: string
  pingMs: number
  dlMbps: number
  ulMbps: number
}

export interface FullRoute {
  id: string
  homeId: string
  wan: WanUplink
  jh: JumpHost
  exit: ExitNode
  hw: WanJhLeg
  je: JhExLeg
  score: number
  confidence: "HIGH" | "MEDIUM" | "LOW"
  probabilityOptimal: number
}

export interface CommRec {
  community: string
  communityName: string
  current: { wan: string; jh: string; exit: string; gateway: string; prob: number } | null
  recommended: { wan: string; jh: string; exit: string; gateway: string; prob: number } | null
  shouldSwitch: boolean
  pinnedBySettings: boolean
}

export interface HomeEntry {
  home: HomeRouter
  wanJhLegs: WanJhLeg[]
  fullRoutes: FullRoute[]
  bestRoute: FullRoute | null
  commRecs: CommRec[]
}

export interface OptimizerData {
  updatedAt: string
  homes: HomeEntry[]
}

export interface OptimizerSettings {
  switchThreshold: number
  hysteresisThreshold: number
  pingWeight: number
  probeIntervalMin: number
  autoApply: boolean
  autoApplyIntervalMin: number
}

/**
 * Значения по умолчанию для Route AI (как в настройках оптимизации маршрутов).
 * Один источник для `/settings#route-ai` и сброса на `/route-optimizer`.
 */
export const DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS: OptimizerSettings = {
  switchThreshold: 15,
  hysteresisThreshold: 10,
  pingWeight: 60,
  probeIntervalMin: 15,
  autoApply: false,
  autoApplyIntervalMin: 60,
}

/** Строка GET /api/servers (фрагмент, нужные поля). */
export interface OptimizerApiServer {
  id: number
  name: string
  host: string
  type: "jump-host" | "exit-node" | "home-router"
  site: string
  country: string
  enabled: boolean
  status: "online" | "offline" | null
  latency: number | null
  model: string | null
  wanUplinks?: Array<{
    id: string
    name: string
    isp: string
    iface: string
    ip: string
    maxDl: number
    maxUl: number
  }>
}

export interface FiltersRulesetRow {
  serverId: string
  rules: FilterRule[]
}

/** Снимок speed-probe из /api/uptime/speed-probes (live-источник ping/speed по интерфейсам). */
export interface RouteOptimizerSpeedProbe {
  id: string
  srcServerId: string
  dstServerId: string
  srcInterface: string
  dstInterface: string
  enabled: boolean
  lastPingRttMs: number | null
  lastPingLossPct: number | null
  lastTxAvgMbps: number | null
  lastRxAvgMbps: number | null
  lastPingAt: string | null
}

/**
 * Единый расчёт score для Route AI (используется в /route-optimizer и связанных оптимизаторах).
 * pingWeight = вес ping в процентах (0..100), оставшийся вес идёт в speed.
 */
export function calcRouteScore(pingMs: number, dlMbps: number, ulMbps: number, pingWeight: number) {
  const pingScore = Math.max(0, 100 - pingMs * 0.6)
  const speedScore = Math.min(100, (dlMbps + ulMbps) / 18)
  const w = pingWeight / 100
  return Math.round(w * pingScore + (1 - w) * speedScore)
}

function confidenceFromProb(prob: number): "HIGH" | "MEDIUM" | "LOW" {
  return prob >= 55 ? "HIGH" : prob >= 30 ? "MEDIUM" : "LOW"
}

function syntheticWans(home: OptimizerApiServer): WanUplink[] {
  return [{
    id: `w-${home.id}-1`,
    name: "WAN1",
    isp: "—",
    iface: "auto",
    ip: home.host,
    maxDl: 1000,
    maxUl: 1000,
  }]
}

function normalizeApiWanUplinks(list: OptimizerApiServer["wanUplinks"]): WanUplink[] {
  const src = Array.isArray(list) ? list : []
  const out = src
    .map((w, idx) => ({
      id: String(w.id || `api-w-${idx + 1}`),
      name: String(w.name || `WAN${idx + 1}`),
      isp: String(w.isp || "—"),
      iface: String(w.iface || "").trim(),
      ip: String(w.ip || ""),
      maxDl: Math.max(1, Math.round(Number(w.maxDl) || 1000)),
      maxUl: Math.max(1, Math.round(Number(w.maxUl) || 1000)),
    }))
    .filter((w) => w.iface.length > 0)
  return out
}

function catalogWansForHome(homeId: string, homeHost?: string, homeName?: string): WanUplink[] | null {
  const hostNorm = (homeHost ?? "").trim().toLowerCase()
  const nameNorm = (homeName ?? "").trim().toLowerCase()
  const row = servers.find((s) => {
    if (s.type !== "home-router") return false
    if (String(s.id) === homeId) return true
    if (hostNorm && String(s.host ?? "").trim().toLowerCase() === hostNorm) return true
    if (nameNorm && String(s.name ?? "").trim().toLowerCase() === nameNorm) return true
    return false
  })
  const list = row?.wanUplinks ?? []
  if (!list.length) return null
  return list.map((w, idx) => ({
    id: w.id || `w-${homeId}-${idx + 1}`,
    name: w.name || `WAN${idx + 1}`,
    isp: w.isp || "—",
    iface: w.iface || "",
    ip: w.ip || "",
    maxDl: Math.max(1, Math.round(Number(w.maxDl) || 1000)),
    maxUl: Math.max(1, Math.round(Number(w.maxUl) || 1000)),
  }))
}

function legPing(
  a: number | null,
  b: number | null,
  aOk: boolean,
  bOk: boolean,
): number {
  if (!aOk || !bOk) return 999
  const x = a ?? 70
  const y = b ?? 70
  return Math.max(1, Math.round((x + y) / 2))
}

function legBandwidthMbps(pingMs: number, capDl: number, capUl: number): { dl: number; ul: number } {
  const factor = Math.max(0.15, 1 - Math.min(pingMs, 200) / 250)
  return {
    dl: Math.max(10, Math.round(Math.min(capDl, capDl * factor))),
    ul: Math.max(10, Math.round(Math.min(capUl, capUl * factor))),
  }
}

/** Экспорт для UI: список JH / Exit после фильтрации. */
export function mapApiServersToTopology(rows: OptimizerApiServer[]): {
  homes: HomeRouter[]
  jumpHosts: JumpHost[]
  exitNodes: ExitNode[]
  byId: Map<string, OptimizerApiServer>
} {
  const byId = new Map<string, OptimizerApiServer>()
  for (const s of rows) {
    if (!s.enabled) continue
    byId.set(String(s.id), s)
  }

  const homes: HomeRouter[] = rows
    .filter((s) => s.enabled && s.type === "home-router")
    .map((s) => ({
      id: String(s.id),
      label: s.name || s.host,
      site: s.site || "—",
      country: s.country || "UN",
      model: s.model ?? "—",
      ip: s.host,
      wans:
        normalizeApiWanUplinks(s.wanUplinks).length > 0
          ? normalizeApiWanUplinks(s.wanUplinks)
          : (catalogWansForHome(String(s.id), s.host, s.name) ?? syntheticWans(s)),
    }))

  const jumpHosts: JumpHost[] = rows
    .filter((s) => s.enabled && s.type === "jump-host" && s.status !== "offline")
    .map((s) => ({
      id: String(s.id),
      label: s.name || s.host,
      site: s.site || "—",
      country: s.country || "UN",
      ip: s.host,
    }))

  const exitNodes: ExitNode[] = rows
    .filter((s) => s.enabled && s.type === "exit-node" && s.status !== "offline")
    .map((s) => ({
      id: String(s.id),
      label: s.name || s.host,
      site: s.site || "—",
      country: s.country || "UN",
      ip: s.host,
    }))

  return { homes, jumpHosts, exitNodes, byId }
}

/**
 * Строит OptimizerData из живых серверов (latency/status из опроса),
 * без случайного jitter — детерминированный ранг маршрутов.
 */
export function buildLiveOptimizerData(
  rows: OptimizerApiServer[],
  rulesets: FiltersRulesetRow[] | null,
  settings: OptimizerSettings,
  speedProbes: RouteOptimizerSpeedProbe[] = [],
): OptimizerData {
  const { homes, jumpHosts, exitNodes, byId } = mapApiServersToTopology(rows)
  const pw = settings.pingWeight

  const flatRules: Array<FilterRule & { sourceServerId: string }> = []
  if (rulesets) {
    for (const rs of rulesets) {
      for (const r of rs.rules ?? []) {
        flatRules.push({ ...r, sourceServerId: rs.serverId })
      }
    }
  }

  const homesOut: HomeEntry[] = homes.map((home) => {
    const homeRow = byId.get(home.id)
    const homeOnline = homeRow?.status === "online"
    const homeLat = homeRow?.latency ?? null

    const wanJhLegs: WanJhLeg[] = []
    const probesForHome = speedProbes
      .filter((p) => p.enabled !== false && p.srcServerId === home.id)

    function scoreProbeForWanIface(p: RouteOptimizerSpeedProbe, wanIface: string): number {
      const want = wanIface.trim().toLowerCase()
      const got = String(p.srcInterface ?? "").trim().toLowerCase()
      if (got && want && got !== want) return -1
      const ifaceScore =
        got && want && got === want ? 100
          : (!got && want) ? 35
            : (!want && got) ? 15
              : 5
      const freshness =
        p.lastPingAt ? Math.max(0, 20 - Math.floor((Date.now() - Date.parse(p.lastPingAt)) / (60 * 60 * 1000))) : 0
      return ifaceScore
        + (p.lastPingRttMs != null ? 20 : 0)
        + (p.lastTxAvgMbps != null ? 10 : 0)
        + (p.lastRxAvgMbps != null ? 10 : 0)
        + freshness
    }

    function assignProbesForWanJh(jhId: string): Map<string, RouteOptimizerSpeedProbe | undefined> {
      const out = new Map<string, RouteOptimizerSpeedProbe | undefined>()
      const pool = probesForHome.filter((p) => p.dstServerId === jhId)
      if (!pool.length) {
        for (const wan of home.wans) out.set(wan.id, undefined)
        return out
      }

      let bestSum = -Infinity
      let bestAssign: Map<string, RouteOptimizerSpeedProbe> | null = null
      const wanList = [...home.wans]

      function dfs(i: number, used: Set<string>, cur: Map<string, RouteOptimizerSpeedProbe>, sum: number) {
        if (i === wanList.length) {
          if (sum > bestSum) {
            bestSum = sum
            bestAssign = new Map(cur)
          }
          return
        }
        const wan = wanList[i]!
        let picked = false
        for (const p of pool) {
          if (used.has(p.id)) continue
          const sc = scoreProbeForWanIface(p, wan.iface)
          if (sc < 0) continue
          picked = true
          used.add(p.id)
          cur.set(wan.id, p)
          dfs(i + 1, used, cur, sum + sc)
          cur.delete(wan.id)
          used.delete(p.id)
        }
        if (!picked) dfs(i + 1, used, cur, sum)
      }

      if (wanList.length <= 7 && wanList.length <= pool.length) {
        dfs(0, new Set(), new Map(), 0)
      }

      if (bestAssign) {
        const best: Map<string, RouteOptimizerSpeedProbe> = bestAssign as Map<string, RouteOptimizerSpeedProbe>
        for (const wan of home.wans) out.set(wan.id, best.get(wan.id))
        return out
      }

      const used = new Set<string>()
      for (const wan of home.wans) {
        let best: RouteOptimizerSpeedProbe | undefined
        let bestScore = -1
        for (const p of pool) {
          if (used.has(p.id)) continue
          const sc = scoreProbeForWanIface(p, wan.iface)
          if (sc > bestScore) {
            bestScore = sc
            best = p
          }
        }
        if (best && bestScore >= 0) {
          out.set(wan.id, best)
          used.add(best.id)
        } else {
          out.set(wan.id, undefined)
        }
      }
      return out
    }

    function pickProbeForJhExit(jhId: string, exitId: string): RouteOptimizerSpeedProbe | undefined {
      const pool = speedProbes.filter((p) => {
        if (p.enabled === false) return false
        const a = p.srcServerId === jhId && p.dstServerId === exitId
        const b = p.srcServerId === exitId && p.dstServerId === jhId
        return a || b
      })
      if (!pool.length) return undefined
      let best: RouteOptimizerSpeedProbe | undefined
      let bestScore = -1
      for (const p of pool) {
        const forward = p.srcServerId === jhId && p.dstServerId === exitId
        const freshness =
          p.lastPingAt ? Math.max(0, 20 - Math.floor((Date.now() - Date.parse(p.lastPingAt)) / (60 * 60 * 1000))) : 0
        const score =
          (forward ? 5 : 0)
          + (p.lastPingRttMs != null ? 25 : 0)
          + (p.lastTxAvgMbps != null ? 12 : 0)
          + (p.lastRxAvgMbps != null ? 12 : 0)
          + freshness
        if (score > bestScore) {
          bestScore = score
          best = p
        }
      }
      return best
    }

    for (const jh of jumpHosts) {
      const assignedByWanId = assignProbesForWanJh(jh.id)
      for (const wan of home.wans) {
        const jhRow = byId.get(jh.id)
        const jhOnline = jhRow?.status === "online"
        const probe = assignedByWanId.get(wan.id)
        const modelPing = legPing(homeLat, jhRow?.latency ?? null, homeOnline, !!jhOnline)
        const ping = probe?.lastPingRttMs != null
          ? Math.max(1, Math.round(probe.lastPingRttMs))
          : modelPing
        const modelBw = legBandwidthMbps(ping, wan.maxDl, wan.maxUl)
        const dl = probe?.lastTxAvgMbps != null
          ? Math.max(1, Math.min(wan.maxDl, Math.round(probe.lastTxAvgMbps)))
          : modelBw.dl
        const ul = probe?.lastRxAvgMbps != null
          ? Math.max(1, Math.min(wan.maxUl, Math.round(probe.lastRxAvgMbps)))
          : modelBw.ul
        const loss = probe?.lastPingLossPct != null
          ? Math.max(0, Math.min(100, Math.round(probe.lastPingLossPct)))
          : (!homeOnline || !jhOnline ? 100 : 0)
        wanJhLegs.push({
          wanId: wan.id,
          jhId: jh.id,
          pingMs: ping,
          dlMbps: dl,
          ulMbps: ul,
          loss,
          score: calcRouteScore(ping, dl, ul, pw),
        })
      }
    }

    const jhExMap = new Map<string, JhExLeg>()
    for (const jh of jumpHosts) {
      for (const ex of exitNodes) {
        const jhRow = byId.get(jh.id)
        const exRow = byId.get(ex.id)
        const ok = jhRow?.status === "online" && exRow?.status === "online"
        const probe = pickProbeForJhExit(jh.id, ex.id)
        const modelPing = legPing(jhRow?.latency ?? null, exRow?.latency ?? null, !!ok, !!ok)
        const ping = probe?.lastPingRttMs != null
          ? Math.max(1, Math.round(probe.lastPingRttMs))
          : modelPing
        const modelBw = legBandwidthMbps(ping, 1000, 1000)
        const dl = probe?.lastTxAvgMbps != null
          ? Math.max(1, Math.round(probe.lastTxAvgMbps))
          : modelBw.dl
        const ul = probe?.lastRxAvgMbps != null
          ? Math.max(1, Math.round(probe.lastRxAvgMbps))
          : modelBw.ul
        jhExMap.set(`${jh.id}::${ex.id}`, {
          jhId: jh.id,
          exitId: ex.id,
          pingMs: ping,
          dlMbps: dl,
          ulMbps: ul,
        })
      }
    }

    const fullRoutes: FullRoute[] = []
    for (const wan of home.wans) {
      for (const jh of jumpHosts) {
        for (const ex of exitNodes) {
          const hw = wanJhLegs.find((l) => l.wanId === wan.id && l.jhId === jh.id)
          const je = jhExMap.get(`${jh.id}::${ex.id}`)
          if (!hw || !je) continue
          const totalPing = hw.pingMs + je.pingMs
          const dl = Math.min(hw.dlMbps, je.dlMbps)
          const ul = Math.min(hw.ulMbps, je.ulMbps)
          const score = calcRouteScore(totalPing, dl, ul, pw)
          fullRoutes.push({
            id: `${home.id}-${wan.id}-${jh.id}-${ex.id}`,
            homeId: home.id,
            wan,
            jh,
            exit: ex,
            hw,
            je,
            score,
            confidence: confidenceFromProb(score),
            probabilityOptimal: 0,
          })
        }
      }
    }

    const sorted = [...fullRoutes].sort((a, b) => b.score - a.score)
    const topScore = sorted[0]?.score ?? 1
    sorted.forEach((r, i) => {
      r.probabilityOptimal = Math.max(1, Math.round(topScore * (0.92 ** i)))
    })
    sorted.sort((a, b) => b.probabilityOptimal - a.probabilityOptimal)
    const best = sorted[0] ?? null

    const commRecs: CommRec[] = flatRules.slice(0, 24).map((rule) => {
      const curProb = 38 + (rule.community.length % 12)
      const recProb = best ? Math.min(92, curProb + 18 + (best.score % 10)) : curProb
      const rec = best
        ? {
            wan: best.wan.name,
            jh: best.jh.label,
            exit: best.exit.label,
            gateway: rule.gateway || "—",
            prob: recProb,
          }
        : null
      const cur = best
        ? {
            wan: home.wans[0]?.name ?? "WAN1",
            jh: jumpHosts[0]?.label ?? "—",
            exit: exitNodes[0]?.label ?? "—",
            gateway: rule.gateway || "—",
            prob: curProb,
          }
        : null
      const diff = (rec?.prob ?? 0) - (cur?.prob ?? 0)
      return {
        community: rule.community,
        communityName: rule.communityName ?? rule.community,
        current: cur,
        recommended: rec,
        shouldSwitch: diff >= settings.switchThreshold,
        pinnedBySettings: false,
      }
    })

    return {
      home,
      wanJhLegs,
      fullRoutes: sorted,
      bestRoute: best,
      commRecs,
    }
  })

  return {
    updatedAt: new Date().toLocaleTimeString("ru-RU"),
    homes: homesOut,
  }
}

/** Частичные настройки из внешнего сервиса (Router Lists route-ai и т.п.). */
export type RouteAiRemotePatch = Partial<{
  switchThreshold: number
  hysteresisThreshold: number
  pingWeight: number
  probeIntervalMin: number
  autoApply: boolean
  autoApplyIntervalMin: number
}>

function clamp(n: number, lo: number, hi: number) {
  return Math.min(hi, Math.max(lo, n))
}

export function mergeOptimizerSettings(
  base: OptimizerSettings,
  patch: RouteAiRemotePatch,
): OptimizerSettings {
  const next = { ...base }
  if (typeof patch.switchThreshold === "number") {
    next.switchThreshold = clamp(Math.round(patch.switchThreshold), 0, 50)
  }
  if (typeof patch.hysteresisThreshold === "number") {
    next.hysteresisThreshold = clamp(Math.round(patch.hysteresisThreshold), 0, 50)
  }
  if (typeof patch.pingWeight === "number") {
    next.pingWeight = clamp(Math.round(patch.pingWeight), 0, 100)
  }
  if (typeof patch.probeIntervalMin === "number") {
    next.probeIntervalMin = clamp(Math.round(patch.probeIntervalMin), 1, 240)
  }
  if (typeof patch.autoApply === "boolean") next.autoApply = patch.autoApply
  if (typeof patch.autoApplyIntervalMin === "number") {
    next.autoApplyIntervalMin = clamp(Math.round(patch.autoApplyIntervalMin), 5, 1440)
  }
  return next
}

/** localStorage: настройки блока «Настройки оптимизатора» на `/route-optimizer`. */
export const ROUTE_OPTIMIZER_SETTINGS_STORAGE_KEY = "routerlists:route-optimizer-settings"

/** Читает сохранённые пороги/веса (только в браузере). */
export function readStoredRouteOptimizerSettings(): OptimizerSettings {
  if (typeof window === "undefined") return DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS
  try {
    const raw = localStorage.getItem(ROUTE_OPTIMIZER_SETTINGS_STORAGE_KEY)
    if (!raw) return DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS
    const p = JSON.parse(raw) as unknown
    if (typeof p !== "object" || p === null) return DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS
    return mergeOptimizerSettings(
      DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS,
      p as RouteAiRemotePatch,
    )
  } catch {
    return DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS
  }
}

