"""
Manual y test de NVR PNI House IP816 usados como VISORES (7 unidades).

El NVR principal graba/ve todo. Estos 7 solo muestran en TV.
IP816: 16 ch x 5MP O 10 ch x 8MP 4K; RJ45 10/100 Mbps (no Gigabit);
ONVIF; HDMI/VGA; app HiEasy; menus tipo Xiongmai (IP Channel / Digital).
"""
from __future__ import annotations

from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
from typing import List, Optional

from .models import Device, NetInfo, now_str
from .nvr_focus_report import is_nvr_device
from .pinger import ping_host
from .ports import scan_ports
from .site_inventory import label_for
from .topology import _is_camera, same_l3_subnet


# Texto del manual (HDMI, raton). Claves de menu en ES/EN típicas PNI/Xiongmai.
MANUAL_IP816_VISOR = """
MANUAL DE CONFIGURACION — NVR PNI House IP816 (VISOR, no principal)
================================================================
Modelo: PNI-IP816  |  16 canales IP 5MP  O  10 canales 8MP 4K
Red: RJ45 10/100 Mbps (NO es Gigabit)  |  HDMI + VGA  |  ONVIF  |  HiEasy
Usted tiene 7 de estos como visor de TV. El 8.o es el NVR PRINCIPAL (graba).

REGLA DE ORO DE LOS 7 VISORES
- Cable Ethernet al switch (nunca WiFi).
- Vista en vivo = SUB / Extra stream / Extra stream (NO el Main 5MP).
- Grabacion OFF o minima (graba el principal).
- Modo canales: 16 x 5MP (si el TV debe ver mas de 10 camaras).
  Si pone 10 x 8MP 4K, ese visor SOLO vera 10 camaras.
- Anadir camaras por IP+ONVIF (no fiarse solo de Buscar).
- Mismo usuario/clave que en las Dahua.

------------------------------------------------------------------------------
A. PRIMERA PUESTA EN MARCHA (cada uno de los 7)
------------------------------------------------------------------------------
1. HDMI al TV, 12V, raton USB, CABLE de red al switch (mismo que Askey/camaras).
2. Asistente inicial: idioma Español, fecha/hora, zona, cambiar clave admin del NVR.
3. Red del NVR (clic derecho → Main Menu / Menu principal → Network / Red):
   - DHCP OFF. IP fija unica, ejemplo real de esta casa:
     NVR1 Salon 192.168.1.62 (principal sugerido)
     NVR2 Salon 192.168.1.64
     NVR Dormitorio 192.168.1.76
     NVR Escalera 192.168.1.57
     Mascara 255.255.255.0  Gateway 192.168.1.1  DNS 192.168.1.1
   - NO active 'adaptive IP' / IP adaptativa en camaras (Close adaptive).
4. Si NVR/ConfigTool solo ven ~7 camaras al inicio: Cudy FS1018 VLAN OFF
   (boton app «Switches / 7 cams»). No es el menu del NVR primero.

------------------------------------------------------------------------------
B. TIPO DE CANAL (imprescindible)
------------------------------------------------------------------------------
Menu → Digital → Channel Type / Tipo de canal
- Elija 16 canales x 5MP (no 10 x 8MP) si en ese TV quiere ver 11–16 camaras.
- El IP816 NO puede 16 camaras a 8MP. 22 camaras en un solo visor: elija las 16
  mas importantes o use paginas/vistas; el principal es quien 'tiene todas'.

------------------------------------------------------------------------------
C. AÑADIR CAMARAS (Canal IP / IP Channel / Digital channels)
------------------------------------------------------------------------------
1. Clic derecho → Main Menu → IP Channel  (o Digital → Digital channels).
2. Protocolo: ONVIF (no NETIP salvo camaras PNI nativas).
3. Search / Buscar: anote las que salen.
4. Las que NO salgan: Add / Agregar manual:
   - IP de la camara (la del informe / escaneo)
   - Puerto 80 (ONVIF). Usuario y clave de la CAMARA Dahua (no la del NVR).
   - Canal vacio 1..16.
5. Channel status: deben pasar a 'conectado' / online.
6. Si 'red inalcanzable' con ping OK desde PC: borre canal, re-añada ONVIF,
   compruebe usuario/clave y que el NVR esta en 192.168.1.x por cable.

------------------------------------------------------------------------------
D. SIN LAG EN EL TV — STREAM DE VISUALIZACION (los 7 iguales)
------------------------------------------------------------------------------
Menu de cada canal o Encode / Codificar / Preview:
- Live / vista previa = Extra stream / Sub stream / Flujo extra.
- NO Main / Principal (5MP) en los visores.
El puerto del IP816 es 100 Mbps: 16 x Main 5MP lo satura el propio NVR
y ademas tumba el WiFi de las 10 Dahua si los 7 visores piden Main.

En camara Dahua (http://IP → Video) o boton «Caudal 12-16 (aplicar)» de la app:
- Extra/Sub: CBR 384-512 kbps, D1 o 640x360, 8-10 fps (esto es lo que vera el TV).
- Main: 1024-1536 kbps 720p CBR (solo lo necesita el NVR principal si graba).
16 x Extra 512 kbps ≈ 8 Mbps: cabe en el puerto 100M del IP816.

------------------------------------------------------------------------------
E. GRABACION EN LOS 7 VISORES
------------------------------------------------------------------------------
Menu → Record / Grabacion:
- Mode: Disable / Cerrar  O  no programar nada.
- Si deja grabacion MAIN de 16 ch en 7 NVR, la red no sera fluida.
El disco SATA es opcional en visores; el principal es quien archiva.

------------------------------------------------------------------------------
F. PANTALLA DEL TV
------------------------------------------------------------------------------
- Vista 4/9/16 segun gusto. HDMI.
- Salida HDMI del IP816: max. reproduccion tipica 2560x1440; el TV 4K
  no obliga a pedir 8MP a las camaras.
- Audio: opcional; para fluidez mejor video solo.

------------------------------------------------------------------------------
G. COMPROBACION RAPIDA EN HDMI (30 s por visor)
------------------------------------------------------------------------------
[ ] LED de red encendido, cable al switch
[ ] IP del NVR distinta a las demas (no .1, no IP de camara)
[ ] Channel Type = 16 x 5MP (si necesita >10 camaras)
[ ] Camaras en verde / conectadas
[ ] Vista fluida = extra/sub (si hay tirones: sigue en Main)
[ ] Grabacion desactivada
[ ] Sin 'SEÑAL EXC' masivo; si hay 1–2, ver Conflictos IP / Informe NVR

------------------------------------------------------------------------------
H. APP HiEasy
------------------------------------------------------------------------------
Opcional. No sustituye el HDMI. No deje P2P/cloud si no lo necesita.
""".strip()


@dataclass
class VisorCheck:
    ip: str
    mac: str
    ok_ping: bool
    latency_ms: Optional[float]
    ports: dict
    same_subnet: bool
    likely_wifi: bool
    score: int
    issues: List[str] = field(default_factory=list)
    manual: List[str] = field(default_factory=list)


@dataclass
class Ip816VisorReport:
    generated: str
    principal_ip: str
    visors: List[VisorCheck]
    camera_count: int
    summary: str
    manual: str
    global_steps: List[str]

    def as_text(self) -> str:
        lines = [
            "TEST + MANUAL — 7 NVR PNI House IP816 (VISORES)",
            "=" * 56,
            f"Generado: {self.generated}",
            f"NVR PRINCIPAL (no testear como visor): {self.principal_ip or '(no marcado)'}",
            f"Camaras en inventario: {self.camera_count}  |  Visores testados: {len(self.visors)}",
            "",
            "LIMITE HARDWARE IP816: 16x5MP o 10x8MP | NIC 10/100 Mbps | ONVIF",
            "Los visores DEBEN usar Extra/Sub en vivo y NO grabar Main de 16 ch.",
            "",
            self.summary,
            "",
            "RESULTADO POR VISOR",
            "-" * 40,
        ]
        for v in self.visors:
            st = "APTO" if v.score >= 70 else ("REVISAR" if v.score >= 40 else "MAL")
            lines.append(
                f"\n[{st} {v.score}/100] {label_for(v.ip) or 'NVR'}  {v.ip}  MAC {v.mac or '-'}  "
                f"ping={'OK' if v.ok_ping else 'FAIL'}  lat={v.latency_ms if v.latency_ms is not None else '-'}"
            )
            openp = ",".join(str(p) for p, o in sorted(v.ports.items()) if o) or "ninguno"
            lines.append(f"    Puertos abiertos: {openp}  misma /24 camaras: {v.same_subnet}")
            for iss in v.issues:
                lines.append(f"    ! {iss}")
            if v.manual:
                lines.append("    QUE HACER EN ESTE IP816 (HDMI + raton):")
                for s in v.manual:
                    lines.append(f"      {s}")
        lines.append("")
        lines.append("PASOS GLOBALES (los 7 iguales)")
        lines.append("-" * 40)
        for s in self.global_steps:
            lines.append(s)
        lines.append("")
        lines.append(self.manual)
        return "\n".join(lines)


def _probe_nvr(nvr: Device, cam_ips: List[str], gw: str) -> VisorCheck:
    pr = ping_host(nvr.ip, timeout_ms=800, count=2)
    ports = scan_ports(nvr.ip, [80, 443, 8000, 37777, 34567, 554], timeout=1.2)
    same = any(same_l3_subnet(nvr.ip, c) for c in cam_ips) if cam_ips else True
    lat = pr.rtt_ms if pr.rtt_ms is not None else nvr.latency_ms
    likely_wifi = bool(lat is not None and lat >= 12)
    issues: List[str] = []
    manual: List[str] = []
    score = 100

    if not pr.ok:
        score = 0
        issues.append("No responde ping: visor apagado, otra subred o sin cable.")
        manual.append("HDMI: compruebe 12V y cable RJ45 al switch. Network → IP 192.168.1.x gateway 192.168.1.1")
        return VisorCheck(nvr.ip, nvr.mac, False, lat, ports, same, likely_wifi, score, issues, manual)

    if likely_wifi:
        score -= 35
        issues.append(f"Latencia {lat:.0f} ms: este IP816 parece en WiFi o enlace malo. Debe ir por CABLE.")
        manual.append("Quite WiFi del NVR. Solo Ethernet al switch. El IP816 es 10/100: un mal enlace = lag en el TV.")

    if not same:
        score -= 40
        issues.append("El visor NO esta en la misma /24 que las camaras: no las vera.")
        manual.append(f"Menu Network: IP 192.168.1.x, mascara 255.255.255.0, gateway {gw or '192.168.1.1'}. Reinicie NVR.")

    if not ports.get(37777) and not ports.get(80):
        score -= 15
        issues.append("No se ven puertos 80/37777. El NVR igual muestra HDMI; compruebe firewall o IP correcta.")

    ncam = len(cam_ips)
    if ncam > 16:
        issues.append(
            f"Hay {ncam} camaras en red; este visor solo monta 16 (modo 5MP) o 10 (modo 8MP). "
            "En HDMI elija las 16 del TV o use varias paginas."
        )
        manual.append("Digital → Channel Type = 16 x 5MP. No 10 x 8MP si necesita mas de 10 en este TV.")
    elif ncam > 10:
        issues.append(
            f"{ncam} camaras: si Channel Type esta en 10 x 8MP 4K, este TV NO vera todas. Ponga 16 x 5MP."
        )
        manual.append("Menu Digital → Channel Type → 16 canales 5MP. Luego re-añada canales ONVIF.")

    issues.append(
        "No se puede leer desde el PC si Extra/Sub esta activo (eso es menu HDMI). "
        "Si ESTE TV tiene lag y el principal no: este visor pide Main. Cambie a Extra stream."
    )
    manual.extend(
        [
            "IP Channel: protocolo ONVIF; añadir por IP las que falten (puerto 80, user/clave Dahua).",
            "Encode/Preview de CADA canal: Extra stream / Sub (NO Main).",
            "Record: Disable en este visor (graba el NVR principal).",
            "Channel status: todos 'conectado'. Si 1–2 fallan: Conflictos IP / Informe desde NVR.",
        ]
    )

    # saturacion 100 Mbps teorica si pidiera main
    if ncam >= 12:
        issues.append(
            "NIC 100 Mbps: 12–16 camaras en Main 5MP saturan ESTE visor. Extra/Sub es obligatorio."
        )
        score -= 10

    return VisorCheck(
        ip=nvr.ip,
        mac=nvr.mac,
        ok_ping=True,
        latency_ms=lat,
        ports=ports,
        same_subnet=same,
        likely_wifi=likely_wifi,
        score=max(0, min(100, score)),
        issues=issues,
        manual=manual,
    )


def run_ip816_visor_test(
    devices: List[Device],
    net: Optional[NetInfo],
    principal_ip: str,
) -> Ip816VisorReport:
    nvrs = [d for d in devices if is_nvr_device(d)]
    cams = [d for d in devices if _is_camera(d) or d.device_type == "Camara"]
    cam_ips = [c.ip for c in cams]
    gw = net.gateway if net else ""

    visor_devs = [d for d in nvrs if d.ip != principal_ip]
    visors: List[VisorCheck] = []
    with ThreadPoolExecutor(max_workers=8) as ex:
        visors = list(ex.map(lambda d: _probe_nvr(d, cam_ips, gw), visor_devs))

    n_bad = sum(1 for v in visors if v.score < 40)
    n_mid = sum(1 for v in visors if 40 <= v.score < 70)
    expected = 7
    summary_parts = [
        f"Visores encontrados (distintos del principal {principal_ip or '-'}): {len(visors)} "
        f"(se esperan {expected} IP816 de TV)."
    ]
    if len(visors) < expected:
        summary_parts.append(
            f"Faltan {expected - len(visors)} visor(es) en el escaneo: apagados, sin cable o mal clasificados. "
            "Marque el principal bien y vuelva a ESCANEAR."
        )
    if len(visors) > expected:
        summary_parts.append("Hay mas NVR que 7 visores: confirme cual es el principal (solo uno).")
    if n_bad:
        summary_parts.append(f"{n_bad} visor(es) MAL (red/subred/apagado).")
    if n_mid:
        summary_parts.append(f"{n_mid} visor(es) a REVISAR en HDMI (sub-stream / tipo de canal).")
    if visors and n_bad == 0:
        summary_parts.append(
            "Red de visores alcanzable. El lag en TV se corrige en HDMI: Extra stream + grabacion OFF + 16x5MP."
        )

    global_steps = [
        "1. En la app: marque SOLO el NVR PRINCIPAL (☑) y pulse de nuevo este test.",
        "2. Visores: .64 Salon TV2, .76 Dormitorio, .57 Escalera (+ otros). Cable, GW 192.168.1.1.",
        "2b. Si solo ven ~7 cams: «Switches / 7 cams» — Cudy FS1018 VLAN OFF antes de pelear con el NVR.",
        "3. Channel Type = 16 x 5MP (no 10 x 8MP si quiere >10 camaras en ese TV).",
        "4. IP Channel → ONVIF → Buscar + Agregar manual las que falten (puerto 80, clave Dahua).",
        "5. Preview/Encode: Extra/Sub en TODOS los canales de los 7 visores.",
        "6. Record Disable en los 7. El principal es quien graba.",
        "7. Si un TV va a tirones y el principal no: ese visor sigue en Main → cambie a Extra.",
        "8. IP816 es 10/100 Mbps: no pida 16 x 5MP Main. Sub cabe holgado.",
        "8b. En la app: «Caudal 12-16 (aplicar)» acota Extra 384-512 kbps en las camaras.",
        "9. Tras HDMI: Fluidez/Lag y Plan red perfecta para confirmar la red.",
        "10. Repita este test: ping OK y misma /24 en los 7.",
    ]

    return Ip816VisorReport(
        generated=now_str(),
        principal_ip=principal_ip,
        visors=visors,
        camera_count=len(cams),
        summary=" ".join(summary_parts),
        manual=MANUAL_IP816_VISOR,
        global_steps=global_steps,
    )
