"""Generación de informe HTML local."""
from __future__ import annotations

import html
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional

from .arp_watch import ArpWatcher
from .events import EventLog
from .models import Device, NetInfo, app_root, ensure_dirs, now_str
from .manual_actions import format_manual_section
from .monitor import MonitorEngine, TimelineEvent


def _esc(s) -> str:
    return html.escape(str(s if s is not None else ""))


def build_conclusion(
    devices: List[Device],
    conflicts: list,
    timeline: List[TimelineEvent],
    dhcp_notes: List[str],
) -> tuple:
    """Devuelve (estado, texto conclusión + acciones recomendadas)."""
    critical = []
    warnings = []
    acciones = []

    if conflicts:
        for c in conflicts:
            critical.append(
                f"Posible conflicto de IP en {c.ip}: MAC {c.old_mac} → {c.new_mac} ({c.ts})."
            )
        acciones.append(
            "ACCIÓN (conflicto IP): En el router Askey, reserve IP fija por MAC para cada cámara; "
            "desactive DHCP en el Tenda (solo modo AP); reinicie cámaras y compruebe que cada IP "
            "tiene UNA sola MAC estable en ARP."
        )
    offline = [d for d in devices if d.status == "OFFLINE"]
    degraded = [d for d in devices if d.status == "DEGRADADO"]
    cams = [d for d in devices if d.device_type == "Camara"]

    offs = [t for t in timeline if t.kind == "offline"]
    multi_drop = len({t.ip for t in offs[-10:]}) >= 3
    if multi_drop:
        critical.append(
            "Se observó pérdida de conectividad simultánea/progresiva en varias cámaras, "
            "lo que sugiere un problema común de Wi-Fi/AP/router (no un fallo individual)."
        )
        acciones.append(
            "ACCIÓN (caída múltiple): Saque TODAS las cámaras del SSID del repetidor (_EXT). "
            "Conéctelas solo al Tenda (AP por cable al Askey) o a un SSID CCTV 2,4 GHz único. "
            "NVR siempre por cable Ethernet. Monitorice 15 min tras el cambio."
        )

    if dhcp_notes:
        warnings.extend(dhcp_notes)
        acciones.append(
            "ACCIÓN (DHCP): Deje un único servidor DHCP (Askey). Si hay dos routers dando IP, "
            "habrá cámaras con gateway incorrecto o IPs que cambian."
        )

    for d in degraded:
        warnings.append(f"{d.ip}: pérdida {d.loss_pct:.0f}% / latencia {d.latency_ms} ms.")
    if degraded and not multi_drop:
        acciones.append(
            "ACCIÓN (pérdida/latencia): Baje bitrate (CBR ~1024 kbps main / 512 kbps sub), "
            "fije canal Wi-Fi 1/6/11 a 20 MHz, evite mezclar 5 GHz y repetidores para CCTV."
        )

    if offline:
        warnings.append(f"{len(offline)} dispositivo(s) OFFLINE al generar el informe.")
        if len(offline) == 1 and not multi_drop and not conflicts:
            acciones.append(
                f"ACCIÓN (1 cámara OFFLINE): Revise alimentación, SSID Manual fijo y distancia "
                f"de {offline[0].ip}. Si el portátil tampoco hace ping, no es (solo) culpa del NVR."
            )

    online_cams = [d for d in cams if d.status in ("ONLINE", "DEGRADADO")]
    if offline and online_cams:
        acciones.append(
            "ACCIÓN (NVR 'red inalcanzable' con ping OK desde PC): El fallo está entre NVR y cámara "
            "(ruta/VLAN/carga del NVR) o el NVR perdió la sesión ONVIF. Reinicie canal IP en el NVR; "
            "compruebe que NVR y cámaras están en la misma 192.168.1.x y NVR por cable."
        )

    if critical:
        estado = "PROBLEMA CRÍTICO"
        texto = " ".join(critical)
        if warnings:
            texto += " Advertencias: " + " ".join(warnings[:5])
    elif warnings:
        estado = "ADVERTENCIA"
        texto = " ".join(warnings[:8])
    else:
        estado = "OK"
        texto = (
            "No se detectaron conflictos IP ni patrones de caída múltiple en la ventana analizada. "
            f"Dispositivos: {len(devices)} ({len(cams)} cámaras)."
        )
        acciones.append(
            "Si el NVR sigue fallando con este informe en OK: alargue la monitorización a 30–60 min "
            "mientras ocurre el fallo, o el problema es de protocolo ONVIF/credenciales (no de IP/ping)."
        )

    if acciones:
        texto += " | QUÉ HACER: " + " ".join(acciones[:4])
    return estado, texto


def generate_html_report(
    net: NetInfo,
    devices: List[Device],
    event_log: EventLog,
    monitor: Optional[MonitorEngine] = None,
    arp: Optional[ArpWatcher] = None,
    dhcp_notes: Optional[List[str]] = None,
    topology_text: str = "",
    title: str = "Informe diagnóstico NVR / cámaras IP",
) -> Path:
    ensure_dirs()
    arp = arp or (monitor.arp if monitor else ArpWatcher())
    conflicts = arp.conflicts()
    timeline = list(monitor.timeline) if monitor else []
    dhcp_notes = dhcp_notes or []
    estado, conclusion = build_conclusion(devices, conflicts, timeline, dhcp_notes)
    manual_text = format_manual_section(conflicts, timeline, dhcp_notes, devices)
    manual_html = "<pre style='white-space:pre-wrap;font-size:13px;line-height:1.45'>" + _esc(
        manual_text
    ) + "</pre>"

    rows = []
    for d in devices:
        rows.append(
            "<tr>"
            f"<td>{_esc(d.ip)}</td><td>{_esc(d.mac)}</td><td>{_esc(d.vendor)}</td>"
            f"<td>{_esc(d.hostname)}</td><td>{_esc(d.device_type)}</td>"
            f"<td class='st-{_esc(d.status)}'>{_esc(d.status)}</td>"
            f"<td>{_esc(d.latency_ms)}</td><td>{_esc(f'{d.loss_pct:.0f}%')}</td>"
            f"<td>{_esc(d.last_seen)}</td><td>{_esc(d.notes)}</td>"
            "</tr>"
        )

    ev_rows = []
    for e in event_log.all()[-300:]:
        ev_rows.append(
            f"<tr class='lv-{_esc(e.level)}'><td>{_esc(e.ts)}</td><td>{_esc(e.level)}</td>"
            f"<td>{_esc(e.ip)}</td><td>{_esc(e.event)}</td><td>{_esc(e.details)}</td></tr>"
        )

    tl_rows = []
    for t in timeline[-100:]:
        tl_rows.append(
            f"<tr><td>{_esc(t.ts_str)}</td><td>{_esc(t.ip)}</td>"
            f"<td>{_esc(t.kind.upper())}</td><td>{_esc(t.details)}</td></tr>"
        )

    conf_html = ""
    if conflicts:
        conf_html = "<ul>" + "".join(
            f"<li><b>{_esc(c.ip)}</b>: {_esc(c.old_mac)} → {_esc(c.new_mac)} ({_esc(c.ts)})</li>"
            for c in conflicts
        ) + "</ul>"
    else:
        conf_html = "<p>No se detectaron conflictos IP en la sesión.</p>"

    topo_html = ""
    if topology_text:
        topo_html = (
            "<h2>Topología / vista desde dispositivos</h2>"
            "<div class='box'><pre style='white-space:pre-wrap;font-size:12px;line-height:1.4'>"
            + _esc(topology_text)
            + "</pre></div>"
        )

    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    path = app_root() / "reports" / f"informe_{stamp}.html"

    css = """
    body{font-family:Segoe UI,Arial,sans-serif;margin:24px;color:#1a1a1a;background:#f7f7f7}
    h1{color:#0b3d5c} h2{color:#0b5a7a;border-bottom:1px solid #ccc;padding-bottom:4px}
    .badge{display:inline-block;padding:8px 14px;border-radius:6px;font-weight:700;color:#fff}
    .OK{background:#2e7d32}.ADVERTENCIA{background:#ef6c00}.PROBLEMA{background:#c62828}
    table{border-collapse:collapse;width:100%;background:#fff;margin:12px 0}
    th,td{border:1px solid #ddd;padding:6px 8px;font-size:13px;text-align:left}
    th{background:#0b5a7a;color:#fff}
    .st-OFFLINE{color:#c62828;font-weight:700}
    .st-DEGRADADO{color:#ef6c00;font-weight:700}
    .st-CONFLICTO\\ IP,.st-CONFLICTO{color:#c62828;font-weight:700;background:#ffebee}
    .st-ONLINE{color:#2e7d32;font-weight:700}
    .lv-CRITICAL{background:#ffcdd2}.lv-ERROR{background:#ffe0b2}
    .lv-WARNING{background:#fff9c4}.box{background:#fff;padding:16px;border-radius:8px;margin:12px 0}
    .meta td{border:none;padding:2px 8px}
    """

    badge_class = "OK" if estado == "OK" else ("ADVERTENCIA" if estado == "ADVERTENCIA" else "PROBLEMA")

    doc = f"""<!DOCTYPE html>
<html lang="es"><head><meta charset="utf-8"><title>{_esc(title)}</title>
<style>{css}</style></head><body>
<h1>{_esc(title)}</h1>
<p>Generado: {_esc(now_str())} · NVR Net Diag (local, sin cloud)</p>
<div class="box">
  <span class="badge {badge_class}">ESTADO DE LA RED: {_esc(estado)}</span>
  <p style="margin-top:12px">{_esc(conclusion)}</p>
</div>
<h2>Red del técnico</h2>
<table class="meta">
<tr><td><b>Adaptador</b></td><td>{_esc(net.adapter)}</td></tr>
<tr><td><b>IP local</b></td><td>{_esc(net.ip)}</td></tr>
<tr><td><b>Máscara</b></td><td>{_esc(net.mask)}</td></tr>
<tr><td><b>Gateway</b></td><td>{_esc(net.gateway)}</td></tr>
<tr><td><b>DHCP server</b></td><td>{_esc(net.dhcp_server or '—')}</td></tr>
<tr><td><b>DNS</b></td><td>{_esc(', '.join(net.dns) if net.dns else '—')}</td></tr>
<tr><td><b>CIDR</b></td><td>{_esc(net.cidr)}</td></tr>
<tr><td><b>Admin Windows</b></td><td>{'Sí' if net.is_admin else 'No (algunas funciones ARP avanzadas limitadas)'}</td></tr>
</table>
<h2>Conflictos IP</h2>
{conf_html}
{topo_html}
<h2>Pasos manuales y comandos Windows</h2>
<div class="box">{manual_html}</div>
<h2>Dispositivos ({len(devices)})</h2>
<table>
<tr><th>IP</th><th>MAC</th><th>Fabricante</th><th>Hostname</th><th>Tipo</th>
<th>Estado</th><th>Latencia</th><th>Pérdida</th><th>Última resp.</th><th>Notas</th></tr>
{''.join(rows)}
</table>
<h2>Línea temporal (monitor)</h2>
<table>
<tr><th>Hora</th><th>IP</th><th>Evento</th><th>Detalle</th></tr>
{''.join(tl_rows) if tl_rows else '<tr><td colspan="4">Sin eventos de monitorización</td></tr>'}
</table>
<h2>Registro de eventos</h2>
<table>
<tr><th>Timestamp</th><th>Nivel</th><th>IP</th><th>Evento</th><th>Detalles</th></tr>
{''.join(ev_rows)}
</table>
<p style="color:#666;font-size:12px">Informe generado localmente. No se enviaron datos a Internet.</p>
</body></html>"""

    path.write_text(doc, encoding="utf-8")
    return path
