"""
Plan de capacidad y 'red perfecta' para CCTV multi-NVR.

Con ~8 NVR + ~22 camaras, el limite no es 'si el ping va': es matematica de
ancho de banda (WiFi 2.4 vs cable gigabit) y arquitectura (un DHCP, AP CCTV,
live=sub en todos los NVR).

El equipo (Askey + Cudy 3000 + Tenda + switch PoE) SÍ da para imagen fluida
SI se usa como se indica. Si 8 NVR piden main 5MP a 10 camaras WiFi, es
fisicamente imposible sin lag, da igual el diagnostico.
"""
from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import List, Optional, Tuple

from .models import Device, NetInfo, now_str
from .netinfo import _run
from .topology import _is_camera, _is_nvr, build_inventory, refine_role


# Capacidad practica (no marketing)
WIFI_24_20MHZ_MBPS = 25.0   # aire compartido real con 10 estaciones
CUDY_AX_MBPS = 150.0        # si camaras en 5 GHz / WiFi6; 2.4 sigue limitado
GIGABIT_SWITCH_MBPS = 800.0  # util en LAN
SUB_KBPS = 640
MAIN_WIFI_KBPS = 1536
MAIN_LAN_KBPS = 2048
MAIN_5MP_KBPS = 4096


@dataclass
class CheckItem:
    ok: bool
    name: str
    detail: str
    manual: str


@dataclass
class PerfectLanReport:
    generated: str
    n_nvr: int
    n_wifi: int
    n_lan: int
    n_family: int
    has_tenda: bool
    has_cudy: bool
    has_askey: bool
    wlan: str
    mbps_all_main: float
    mbps_live_sub: float
    wifi_budget_ok_main: bool
    wifi_budget_ok_sub: bool
    lan_budget_ok_main: bool
    verdict: str
    checks: List[CheckItem]
    score: int
    steps: List[str]
    honesty: str

    def as_text(self) -> str:
        lines = [
            "PLAN RED PERFECTA / CAPACIDAD (todos los NVR fluidos)",
            "=" * 56,
            f"Generado: {self.generated}",
            "",
            self.honesty,
            "",
            "INVENTARIO CONTRA EL QUE SE CALCULA",
            "-" * 40,
            f"NVR: {self.n_nvr}  |  Camaras WiFi (est.): {self.n_wifi}  |  Camaras LAN/PoE: {self.n_lan}",
            f"Otros/familia en la LAN: {self.n_family}",
            f"Askey/gateway: {'SI' if self.has_askey else 'no visto'}  "
            f"Tenda: {'SI' if self.has_tenda else 'no visto'}  "
            f"Cudy: {'SI' if self.has_cudy else 'no visto'}",
            "",
            "WI-FI DEL TECNICO (netsh)",
            "-" * 40,
            self.wlan or "(no conectado por WiFi o netsh no disponible)",
            "",
            "MATEMATICA DE ANCHO DE BANDA",
            "-" * 40,
            f"Si TODOS los NVR piden MAIN de TODAS las camaras: ~{self.mbps_all_main:.0f} Mbps",
            f"Si live = SUB (~{SUB_KBPS} kbps) y un NVR graba MAIN: ~{self.mbps_live_sub:.0f} Mbps aire+LAN",
            f"Presupuesto WiFi 2.4 (10 camaras en Tenda, ~{WIFI_24_20MHZ_MBPS:.0f} Mbps reales): "
            f"MAIN x {self.n_nvr} NVR = {'NO CABE' if not self.wifi_budget_ok_main else 'justo/ok'}  |  "
            f"SUB en vivo = {'OK' if self.wifi_budget_ok_sub else 'sigue justo'}",
            f"Switch gigabit para PoE: MAIN x {self.n_nvr} NVR = "
            f"{'OK' if self.lan_budget_ok_main else 'aprieta (baje bitrate o use sub en vivo)'}",
            "",
            "VEREDICTO",
            "-" * 40,
            self.verdict,
            "",
            f"CHECKLIST ARQUITECTURA  ({self.score}/100)",
            "-" * 40,
        ]
        for c in self.checks:
            mark = "OK " if c.ok else "FALTA"
            lines.append(f"[{mark}] {c.name}")
            lines.append(f"       {c.detail}")
            if not c.ok and c.manual:
                lines.append(f"       QUE HACER: {c.manual}")
        lines.append("")
        lines.append("ORDEN DE TRABAJO (con el equipo que ya tiene)")
        lines.append("-" * 40)
        for s in self.steps:
            lines.append(s)
        return "\n".join(lines)


def _wlan_snapshot() -> str:
    out = _run(["netsh", "wlan", "show", "interfaces"])
    if not out or "no hay" in out.lower() and "interfaz" in out.lower():
        return ""
    interesting = []
    keys = (
        "ssid", "bssid", "señal", "signal", "canal", "channel",
        "radio", "receive", "transmit", "recepción", "transmisión",
        "estado", "state", "perfil", "profile",
    )
    for line in out.splitlines():
        low = line.lower()
        if any(k in low for k in keys) and ":" in line:
            interesting.append(line.strip())
    text = "\n".join(interesting[:18]) if interesting else out[:800]
    if re.search(r"_EXT", text, re.I):
        text += "\nALERTA: el tecnico esta en SSID _EXT (repetidor). No diagnostique ni deje camaras ahi."
    return text


def _counts(devices: List[Device], net: Optional[NetInfo]) -> Tuple[int, int, int, int, bool, bool, bool]:
    inv = build_inventory(devices, net)
    nvrs = inv["NVR"]
    cams = inv["Camaras"]
    wifi = lan = 0
    for c in cams:
        role = refine_role(c, net.gateway if net else "")
        if "WiFi" in role:
            wifi += 1
        elif "LAN" in role or "PoE" in role:
            lan += 1
        elif c.latency_ms is not None and c.latency_ms >= 8:
            wifi += 1
        else:
            lan += 1
    family = 0
    has_tenda = has_cudy = has_askey = False
    gw = net.gateway if net else ""
    for d in devices:
        v = (d.vendor or "").lower()
        if "tenda" in v:
            has_tenda = True
        if "cudy" in v:
            has_cudy = True
        if "askey" in v or d.ip == gw:
            has_askey = True
        if d.device_type not in ("Camara", "NVR", "Router/AP", "Switch") and d.ip != gw:
            if "tenda" not in v and "cudy" not in v and "askey" not in v:
                family += 1
    n_nvr = max(len(nvrs), 1)
    return n_nvr, wifi, lan, family, has_tenda, has_cudy, has_askey or bool(gw)


def _bandwidth(n_nvr: int, n_wifi: int, n_lan: int) -> Tuple[float, float, bool, bool, bool]:
    # peor caso: todos piden main 5MP en WiFi y 2 Mbps LAN
    wifi_main = n_wifi * MAIN_5MP_KBPS * n_nvr / 1000.0
    lan_main = n_lan * MAIN_LAN_KBPS * n_nvr / 1000.0
    all_main = wifi_main + lan_main
    # gold: 1 NVR graba main wifi 1536 + lan 2048; el resto solo sub
    wifi_gold = (n_wifi * MAIN_WIFI_KBPS + n_wifi * SUB_KBPS * max(0, n_nvr - 1)) / 1000.0
    lan_gold = (n_lan * MAIN_LAN_KBPS + n_lan * SUB_KBPS * max(0, n_nvr - 1)) / 1000.0
    live_sub = wifi_gold + lan_gold
    wifi_ok_main = wifi_main <= WIFI_24_20MHZ_MBPS
    wifi_ok_sub = (n_wifi * SUB_KBPS * n_nvr / 1000.0) <= WIFI_24_20MHZ_MBPS * 1.2
    lan_ok_main = lan_main <= GIGABIT_SWITCH_MBPS
    return all_main, live_sub, wifi_ok_main, wifi_ok_sub, lan_ok_main


def build_perfect_lan_plan(
    devices: List[Device],
    net: Optional[NetInfo],
    dhcp_dual: bool = False,
    dhcp_server: str = "",
    gateway: str = "",
    fluency_scores: Optional[List[int]] = None,
) -> PerfectLanReport:
    n_nvr, n_wifi, n_lan, n_fam, has_tenda, has_cudy, has_askey = _counts(devices, net)
    all_main, live_sub, w_main, w_sub, l_main = _bandwidth(n_nvr, n_wifi, n_lan)
    gw = gateway or (net.gateway if net else "") or "192.168.1.1"
    dhcp = dhcp_server or (net.dhcp_server if net else "") or ""
    wlan = _wlan_snapshot()

    checks: List[CheckItem] = []

    dhcp_ok = bool(dhcp and gw and dhcp == gw and not dhcp_dual)
    checks.append(
        CheckItem(
            dhcp_ok,
            "Un solo DHCP = Askey (192.168.1.1)",
            f"DHCP visto={dhcp or 's/d'} gateway={gw}. Dual={dhcp_dual}",
            f"Askey http://{gw} DHCP ON. Tenda y Cudy: DHCP OFF, modo AP, cable LAN al Askey.",
        )
    )
    checks.append(
        CheckItem(
            has_tenda or n_wifi == 0,
            "Tenda dedicado a camaras WiFi (SSID CCTV, sin _EXT)",
            "10 camaras WiFi no deben ir al repetidor Movistar ni mezclarse con moviles de la familia en el mismo AP saturado.",
            "SSID unico en Tenda (ej. Tenda_2724C0). Camaras modo Manual. Aislamiento de clientes OFF. Canal 1/6/11 @ 20 MHz.",
        )
    )
    if has_cudy:
        checks.append(
            CheckItem(
                True,
                "Cudy 3000 visto en la LAN (backbone)",
                "Debe ser AP/mesh, DHCP OFF, familia aqui; camaras en Tenda.",
                "Web Cudy → DHCP off, modo AP, cable al Askey.",
            )
        )
    else:
        checks.append(
            CheckItem(
                False,
                "Cudy 3000 no aparece clasificado en el escaneo",
                "Si lo tiene y no sale en 192.168.1.x, puede estar haciendo NAT (red partida).",
                "Cudy: AP, DHCP OFF, LAN al Askey, misma subred. Vuelva a escanear.",
            )
        )
    checks.append(
        CheckItem(
            n_nvr >= 1,
            "Todos los NVR por CABLE al switch (nunca WiFi)",
            f"{n_nvr} NVR detectados. Un NVR en WiFi arruina live de ese TV.",
            "Cada IP816: Ethernet al switch/Askey. Compruebe LED de enlace 1 Gbps.",
        )
    )
    checks.append(
        CheckItem(
            w_sub,
            "Live = SUB-STREAM en TODOS los NVR (imprescindible con 8 TVs)",
            f"MAIN x {n_nvr} NVR en WiFi ~{n_wifi * MAIN_5MP_KBPS * n_nvr / 1000:.0f} Mbps: "
            f"{'no cabe en 2.4' if not w_main else 'limite'}. SUB si cabe: {w_sub}.",
            "En cada NVR PNI: vista en vivo = sub-stream (subtype=1, ~512-768 kbps). "
            "Grabacion MAIN solo en el NVR principal (o MAIN bajo 1536 kbps si graban todos).",
        )
    )
    checks.append(
        CheckItem(
            True if not fluency_scores else (sum(1 for s in fluency_scores if s < 60) == 0),
            "Bitrate camaras WiFi acotado (CBR 1024-1536 kbps, 10-15 fps)",
            "5MP CBR alto en Tenda + 8 NVR = lag seguro. Cable PoE aguanta mas.",
            "Dahua http://IP → Main CBR 1024-1536, 1080p o 720p, 15 fps, I-frame=FPS, H.265. Sub 512-768.",
        )
    )
    checks.append(
        CheckItem(
            n_fam < 80,  # always true; informational
            "Familia fuera del rango IP de camaras",
            f"{n_fam} dispositivos no CCTV en la misma LAN (normal). El riesgo es DHCP libre.",
            f"Askey: reservas camaras .40-.70 por MAC; familia DHCP .100-.200. Sin IPs estaticas duplicadas.",
        )
    )
    wifi_ext = bool(wlan and re.search(r"_EXT", wlan, re.I))
    checks.append(
        CheckItem(
            not wifi_ext,
            "Nadie critico en SSID repetidor _EXT",
            "El repetidor TP-Link parte multicast y genera 'SEÑAL EXC'.",
            "Saque camaras y, si puede, el portatil de MOVISTAR-WIFI6-..._EXT.",
        )
    )

    score = int(100 * sum(1 for c in checks if c.ok) / max(1, len(checks)))

    if not w_main and n_nvr >= 2 and n_wifi >= 6:
        verdict = (
            f"CON este equipo SE PUEDE ver fluido en los {n_nvr} NVR, "
            "pero NO si todos piden el main de las camaras WiFi. "
            "Regla de oro: live=sub en todos; MAIN bajo y/o solo en el NVR que graba. "
            "Cable PoE ya puede ir bien; el cuello es Tenda 2.4 + multi-NVR."
        )
    elif w_sub and l_main:
        verdict = (
            "Capacidad OK si se aplica el checklist (un DHCP, AP CCTV, sub en vivo, bitrate bajo). "
            "El hardware (Cudy 3000 + Tenda + switch + IP816) basta."
        )
    else:
        verdict = "Revise checklist: hay un cuello (WiFi, DHCP dual o demasiados mains)."

    steps = [
        f"1. Askey http://{gw}: DHCP ON. Reservas MAC de las {n_wifi + n_lan} camaras (.40-.70). Familia .100-.200.",
        "2. Tenda: AP, DHCP OFF, LAN-LAN al Askey. SSID CCTV 2.4 GHz canal 1/6/11, 20 MHz, aislamiento OFF.",
        "3. Cudy 3000: AP/mesh, DHCP OFF, cable al Askey. Moviles/TV de la casa al Cudy, NO a las camaras.",
        "4. 12 camaras PoE al switch; 8 NVR al mismo switch por cable (1 Gbps).",
        "5. Dahua: boton «Caudal 12-16» o a mano Main CBR 1024-1536 / Extra 384-512. Guardar.",
        f"6. En los {n_nvr} NVR: Canal IP → stream de visualizacion = sub. Grabacion = main (bitrate ya bajado).",
        "7. NVR principal: confirme las 22 camaras. Si faltan 1-2: Conflictos IP/DHCP + Informe desde NVR.",
        "8. Resto de NVR: mismas IPs añadidas a mano (no solo Buscar) y SIEMPRE sub en vivo.",
        "9. Fluidez/Lag: scores WiFi >= 75. Si no, baje otro tramo de bitrate o acerque al Tenda.",
        "10. Monitorice 20 min en hora punta (familia en WiFi). No debe haber OFFLINE en bloque ni jitter alto.",
        "11. No use _EXT para CCTV. No ponga NVR en WiFi. No deje Tenda en modo router/DHCP.",
        "12. Tras cambios: Plan red perfecta otra vez. Objetivo checklist ~100 y matematica SUB en verde.",
    ]

    honesty = (
        "El programa no 'arregla' solo la red: mide y le dice el unico camino viable con SU equipo. "
        "8 televisores = 8 NVR pidiendo video a la vez. En cable gigabit cabe. En WiFi 2.4 de 10 camaras "
        "NO cabe el main 5MP octuplicado. Por eso live=sub no es un truco: es fisica. "
        "Cudy 3000 ayuda a la casa; las camaras deben quedarse en el Tenda acotado, no mezcladas con moviles."
    )

    return PerfectLanReport(
        generated=now_str(),
        n_nvr=n_nvr,
        n_wifi=n_wifi,
        n_lan=n_lan,
        n_family=n_fam,
        has_tenda=has_tenda,
        has_cudy=has_cudy,
        has_askey=has_askey,
        wlan=wlan,
        mbps_all_main=all_main,
        mbps_live_sub=live_sub,
        wifi_budget_ok_main=w_main,
        wifi_budget_ok_sub=w_sub,
        lan_budget_ok_main=l_main,
        verdict=verdict,
        checks=checks,
        score=score,
        steps=steps,
        honesty=honesty,
    )
