import type { GreTunnel, Server, WanUplink } from "@/lib/data"
import {
  assignSpeedProbesToGreTunnels,
  assignSpeedProbesToWanJhEdges,
  mergeGreMetricsWithSpeedProbe,
  wanJhEdgeMapKey,
} from "@/lib/map-gre-speed-probe"
import {
  buildWanJhEdges,
  findServerByGreRemote,
  greTunnelProbe,
} from "@/lib/network-map-layout"
import {
  buildLiveOptimizerData,
  DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS,
  type FiltersRulesetRow,
  type OptimizerApiServer,
  type RouteOptimizerSpeedProbe,
} from "@/lib/route-optimizer-data"

export interface InternetPathRoute {
  wanId: string
  jhId?: string
  exitId?: string
  reason: string
}

export interface InternetPathHop {
  home: Server
  wan: WanUplink
  jumpHost: Server
  exitNode: Server
  wanJhMetrics: {
    pingMs: number | null
    dlMbps: number | null
    ulMbps: number | null
    fromMonitoring: boolean
  }
  jhExitMetrics: {
    pingMs: number | null
    dlMbps: number | null
    ulMbps: number | null
    fromMonitoring: boolean
  }
}

export interface InternetPathViewModel {
  homeRouter: Server
  activeWanUplink: WanUplink | null
  fallbackJumpHost: Server | null
  fallbackExitNode: Server | null
  primaryHop: InternetPathHop | null
  currentHop: InternetPathHop | null
  primaryPath: InternetPathRoute | null
  currentPath: InternetPathRoute | null
  pathState: "healthy" | "degraded" | "failover" | "unknown"
  directWan: {
    enabled: boolean
    gateway: string | null
    leasedIp: string | null
    iface: string | null
    provider: string | null
  }
}

export interface RouteLookupResult {
  gateway: string | null
  routingMark: string | null
}

export interface HomeWanRuntime {
  defaultGateway: string | null
  defaultInterface: string | null
  uplinks: Array<{
    id: string
    iface: string
    name: string
    isp: string
    configuredIp: string
    leasedIp: string | null
    dhcpStatus: string | null
    isDefault: boolean
  }>
}

const INTERNET_TARGET = "1.1.1.1"

function norm(v: string | null | undefined): string {
  return String(v ?? "").trim().toLowerCase()
}

function parseRouteLookupOutput(output: string): RouteLookupResult {
  const lines = output.split(/\r?\n/)
  let gateway: string | null = null
  let routingMark: string | null = null
  for (const line of lines) {
    const g = line.match(/^\s*gateway:\s*(.+?)\s*$/i)
    if (g) gateway = g[1].trim()
    const rm = line.match(/^\s*routing-mark:\s*(.+?)\s*$/i)
    if (rm) routingMark = rm[1].trim()
  }
  return { gateway, routingMark }
}

function chooseActiveWan(
  home: Server,
  lookup: RouteLookupResult | null,
  runtime: HomeWanRuntime | null,
): WanUplink | null {
  const wans = home.wanUplinks ?? []
  if (!wans.length) return null

  // 1) Истина из MikroTik /ip/route + /ip/dhcp-client (wan-runtime)
  if (runtime) {
    const byDefaultFlag = runtime.uplinks.find((u) => u.isDefault)
    if (byDefaultFlag) {
      const byId = wans.find((w) => norm(w.id) === norm(byDefaultFlag.id))
      if (byId) return byId
      const byIface = wans.find((w) => norm(w.iface) === norm(byDefaultFlag.iface))
      if (byIface) return byIface
    }
    if (runtime.defaultInterface) {
      const byIface = wans.find((w) => norm(w.iface) === norm(runtime.defaultInterface))
      if (byIface) return byIface
    }
    const runtimeByIface = runtime.uplinks.find((u) => norm(u.iface) === norm(runtime.defaultInterface))
    if (runtimeByIface) {
      const byId = wans.find((w) => norm(w.id) === norm(runtimeByIface.id))
      if (byId) return byId
    }
    if (runtime.defaultGateway) {
      const byGatewayIp = wans.find((w) => norm(w.ip) === norm(runtime.defaultGateway))
      if (byGatewayIp) return byGatewayIp
    }
    const runtimeBound = runtime.uplinks.find((u) => (u.dhcpStatus ?? "").toLowerCase() === "bound")
    if (runtimeBound) {
      const byId = wans.find((w) => norm(w.id) === norm(runtimeBound.id))
      if (byId) return byId
      const byIface = wans.find((w) => norm(w.iface) === norm(runtimeBound.iface))
      if (byIface) return byIface
    }
  }

  // 2) Fallback: route-lookup
  const gw = norm(lookup?.gateway)
  if (gw) {
    const byIface = wans.find((w) => norm(w.iface) === gw || gw.includes(norm(w.iface)))
    if (byIface) return byIface
    const byIp = wans.find((w) => norm(w.ip) === gw || gw.startsWith(`${norm(w.ip)}%`))
    if (byIp) return byIp
  }
  return wans[0] ?? null
}

function syntheticWanForHome(home: Server): WanUplink {
  return {
    id: `wan-auto-${home.id}`,
    name: "WAN-AUTO",
    isp: "auto",
    iface: "auto",
    ip: home.host || "0.0.0.0",
    maxDl: 100,
    maxUl: 100,
  }
}

function bestJhForWan(
  home: Server,
  wan: WanUplink,
  jhs: Server[],
  probes: RouteOptimizerSpeedProbe[],
): Server | null {
  const pool = probes.filter((p) => p.srcServerId === home.id && p.enabled !== false)
  const ranked = jhs
    .map((jh) => {
      const m = pool
        .filter((p) => p.dstServerId === jh.id)
        .filter((p) => {
          const iface = norm(p.srcInterface)
          return !iface || iface === norm(wan.iface)
        })
      const best = m.sort((a, b) => (a.lastPingRttMs ?? 9999) - (b.lastPingRttMs ?? 9999))[0]
      const rtt = best?.lastPingRttMs ?? jh.latency ?? 9999
      return { jh, rtt }
    })
    .sort((a, b) => a.rtt - b.rtt)
  return ranked[0]?.jh ?? null
}

function bestExitForJh(jh: Server, exits: Server[], probes: RouteOptimizerSpeedProbe[]): Server | null {
  const pool = probes.filter((p) => p.enabled !== false)
  const ranked = exits
    .map((ex) => {
      const best = pool
        .filter((p) => (
          (p.srcServerId === jh.id && p.dstServerId === ex.id) ||
          (p.srcServerId === ex.id && p.dstServerId === jh.id)
        ))
        .sort((a, b) => (a.lastPingRttMs ?? 9999) - (b.lastPingRttMs ?? 9999))[0]
      const rtt = best?.lastPingRttMs ?? ex.latency ?? 9999
      return { ex, rtt }
    })
    .sort((a, b) => a.rtt - b.rtt)
  return ranked[0]?.ex ?? null
}

function pickHomeJhProbe(
  homeId: string,
  wanIface: string,
  jhId: string,
  probes: RouteOptimizerSpeedProbe[],
): RouteOptimizerSpeedProbe | null {
  const ifaceNorm = norm(wanIface)
  const list = probes
    .filter((p) => p.enabled !== false)
    .filter((p) => p.srcServerId === homeId && p.dstServerId === jhId)
    .filter((p) => {
      const srcIf = norm(p.srcInterface)
      return !ifaceNorm || !srcIf || srcIf === ifaceNorm
    })
    .sort((a, b) => {
      const aHasPing = a.lastPingRttMs != null ? 1 : 0
      const bHasPing = b.lastPingRttMs != null ? 1 : 0
      if (bHasPing !== aHasPing) return bHasPing - aHasPing
      return (a.lastPingRttMs ?? 9999) - (b.lastPingRttMs ?? 9999)
    })
  return list[0] ?? null
}

function pickJhExitProbe(jhId: string, exitId: string, probes: RouteOptimizerSpeedProbe[]): RouteOptimizerSpeedProbe | null {
  const list = probes
    .filter((p) => p.enabled !== false)
    .filter((p) => (
      (p.srcServerId === jhId && p.dstServerId === exitId) ||
      (p.srcServerId === exitId && p.dstServerId === jhId)
    ))
    .sort((a, b) => {
      const aHasPing = a.lastPingRttMs != null ? 1 : 0
      const bHasPing = b.lastPingRttMs != null ? 1 : 0
      if (bHasPing !== aHasPing) return bHasPing - aHasPing
      return (a.lastPingRttMs ?? 9999) - (b.lastPingRttMs ?? 9999)
    })
  return list[0] ?? null
}

function metricValue(primary: number | null | undefined, secondary: number | null | undefined): number | null {
  return primary ?? secondary ?? null
}

export function buildDashboardInternetPath(args: {
  servers: Server[]
  greTunnels: GreTunnel[]
  probes: RouteOptimizerSpeedProbe[]
  filtersRulesets: FiltersRulesetRow[]
  routeLookupByServerId: Record<string, RouteLookupResult | null>
  wanRuntimeByHomeId?: Record<string, HomeWanRuntime | null>
}): InternetPathViewModel | null {
  const homes = args.servers.filter((s) => s.type === "home-router" && s.enabled)
  const jhs = args.servers.filter((s) => s.type === "jump-host" && s.enabled && s.status !== "offline")
  const exits = args.servers.filter((s) => s.type === "exit-node" && s.enabled && s.status !== "offline")
  const home = homes[0]
  if (!home || !jhs.length || !exits.length) return null
  const runtime = args.wanRuntimeByHomeId?.[home.id] ?? null

  const optimizerRows: OptimizerApiServer[] = args.servers.map((s) => ({
    id: Number.parseInt(s.id, 10) || 0,
    name: s.name,
    host: s.host,
    type: s.type,
    site: s.site,
    country: s.country,
    enabled: s.enabled,
    status: s.status === "degraded" ? "online" : s.status,
    latency: s.latency,
    model: s.model ?? null,
    wanUplinks: (s.wanUplinks ?? []).map((w) => ({ ...w })),
  }))
  const optimizerData = buildLiveOptimizerData(
    optimizerRows,
    args.filtersRulesets,
    DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS,
    args.probes,
  )
  const homeOptimizer = optimizerData.homes.find((h) => h.home.id === home.id)
  const best = homeOptimizer?.bestRoute ?? null

  const primaryPath: InternetPathRoute | null = best
    ? {
        wanId: best.wan.id,
        jhId: best.jh.id,
        exitId: best.exit.id,
        reason: "Route optimizer / OSPF quality",
      }
    : null

  const activeWan = chooseActiveWan(
    home,
    args.routeLookupByServerId[home.id] ?? null,
    runtime,
  ) ?? syntheticWanForHome(home)
  const currentJh = activeWan ? bestJhForWan(home, activeWan, jhs, args.probes) : null
  const currentExit = currentJh ? bestExitForJh(currentJh, exits, args.probes) : null
  const currentPath: InternetPathRoute | null = activeWan
    ? {
        wanId: activeWan.id,
        reason: `Default route 0.0.0.0/0 (main) via ${activeWan.iface || activeWan.ip || "WAN"}`,
      }
    : null

  const pathState: InternetPathViewModel["pathState"] =
    primaryPath && currentPath
      // Нормальный production-кейс: часть трафика идет через BGP (JH→EN),
      // часть — напрямую в провайдера WAN uplink.
      ? "healthy"
      : (!primaryPath && !currentPath ? "unknown" : "degraded")

  const primaryWan = (home.wanUplinks ?? []).find((w) => w.id === primaryPath?.wanId) ?? null
  const primaryJh = args.servers.find((s) => s.id === primaryPath?.jhId) ?? null
  const primaryEx = args.servers.find((s) => s.id === primaryPath?.exitId) ?? null
  const currentWan = (home.wanUplinks ?? []).find((w) => w.id === currentPath?.wanId) ?? activeWan

  const primaryHop: InternetPathHop | null =
    primaryWan && primaryJh && primaryEx
      ? {
          home,
          wan: primaryWan,
          jumpHost: primaryJh,
          exitNode: primaryEx,
          wanJhMetrics: {
            pingMs: primaryJh.latency,
            dlMbps: primaryWan.maxDl,
            ulMbps: primaryWan.maxUl,
            fromMonitoring: false,
          },
          jhExitMetrics: {
            pingMs: primaryEx.latency,
            dlMbps: null,
            ulMbps: null,
            fromMonitoring: false,
          },
        }
      : null

  const currentHop: InternetPathHop | null =
    currentWan && currentJh && currentExit
      ? {
          home,
          wan: currentWan,
          jumpHost: currentJh,
          exitNode: currentExit,
          wanJhMetrics: (() => {
            const wanJh = buildWanJhEdges(args.servers).find((e) =>
              e.homeId === home.id
              && e.jhId === currentJh.id
              && (home.wanUplinks?.[e.wanIdx]?.id ?? "") === currentWan.id,
            )
            const fallback = {
              pingMs: wanJh?.pingMs ?? currentJh.latency,
              dlMbps: wanJh?.dlMbps ?? currentWan.maxDl,
              ulMbps: currentWan.maxUl,
            }
            if (!wanJh) return { ...fallback, fromMonitoring: false }
            const byEdge = assignSpeedProbesToWanJhEdges([wanJh], args.servers, args.probes)
            const sp = byEdge.get(wanJhEdgeMapKey(wanJh))
            const merged = mergeGreMetricsWithSpeedProbe(sp, fallback)
            return {
              pingMs: merged.pingMs,
              dlMbps: metricValue(merged.dlMbps, merged.ulMbps),
              ulMbps: metricValue(merged.ulMbps, merged.dlMbps),
              fromMonitoring: merged.hasSpeedMonitor,
            }
          })(),
          jhExitMetrics: (() => {
            const tunnel = args.greTunnels.find((t) => {
              const from = args.servers.find((s) => s.id === String(t.serverId))
              if (!from) return false
              const to = findServerByGreRemote(args.servers, t.remoteAddress)
              if (!to) return false
              return (
                (from.id === currentJh.id && to.id === currentExit.id)
                || (from.id === currentExit.id && to.id === currentJh.id)
              )
            })
            const fallback = tunnel
              ? greTunnelProbe(tunnel)
              : { pingMs: currentExit.latency, dlMbps: null, ulMbps: null }
            if (!tunnel) {
              return {
                pingMs: fallback.pingMs,
                dlMbps: fallback.dlMbps,
                ulMbps: fallback.ulMbps,
                fromMonitoring: false,
              }
            }
            const fromServer = args.servers.find((s) => s.id === String(tunnel.serverId))
            const toServer = fromServer ? findServerByGreRemote(args.servers, tunnel.remoteAddress) : undefined
            if (!fromServer || !toServer) {
              return {
                pingMs: fallback.pingMs,
                dlMbps: fallback.dlMbps,
                ulMbps: fallback.ulMbps,
                fromMonitoring: false,
              }
            }
            const byTunnel = assignSpeedProbesToGreTunnels(
              [{ tunnel, fromServer, toServer }],
              args.probes,
            )
            const sp = byTunnel.get(tunnel.id)
            const merged = mergeGreMetricsWithSpeedProbe(sp, fallback)
            return {
              pingMs: merged.pingMs,
              dlMbps: metricValue(merged.dlMbps, merged.ulMbps),
              ulMbps: metricValue(merged.ulMbps, merged.dlMbps),
              fromMonitoring: merged.hasSpeedMonitor,
            }
          })(),
        }
      : null

  return {
    homeRouter: home,
    activeWanUplink: activeWan,
    fallbackJumpHost: currentJh ?? primaryJh ?? jhs[0] ?? null,
    fallbackExitNode: currentExit ?? primaryEx ?? exits[0] ?? null,
    primaryHop,
    currentHop,
    primaryPath,
    currentPath,
    pathState,
    directWan: {
      enabled: Boolean(runtime?.defaultGateway || args.routeLookupByServerId[home.id]?.gateway),
      gateway: runtime?.defaultGateway ?? args.routeLookupByServerId[home.id]?.gateway ?? null,
      iface: runtime?.defaultInterface ?? activeWan.iface ?? null,
      leasedIp:
        runtime?.uplinks.find((u) => u.isDefault)?.leasedIp
        ?? runtime?.uplinks.find((u) => norm(u.iface) === norm(activeWan.iface))?.leasedIp
        ?? null,
      provider:
        runtime?.uplinks.find((u) => u.isDefault)?.isp
        ?? runtime?.uplinks.find((u) => norm(u.iface) === norm(activeWan.iface))?.isp
        ?? activeWan.isp
        ?? null,
    },
  }
}

export async function resolveDefaultRouteLookup(
  apiFetch: <T>(path: string, init?: RequestInit) => Promise<T>,
  serverId: string,
): Promise<RouteLookupResult | null> {
  try {
    const payload = await apiFetch<{ output?: string }>(`/api/servers/${serverId}/probes/run`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        tool: "route",
        target: INTERNET_TARGET,
      }),
    })
    return parseRouteLookupOutput(payload.output ?? "")
  } catch {
    return null
  }
}
