"""Tests para traceroute_host (CLI `traceroute`, estilo dict sin excepciones).

Sin red: se monkeypatchea ``subprocess.run`` en el namespace del modulo
``traceroute_host`` para devolver salidas fijas o lanzar excepciones.
"""

import os
import subprocess
import sys

sys.path.insert(0, os.path.dirname(__file__))

import traceroute_host as tr_mod
from traceroute_host import traceroute_host

# Salida real de traceroute: cabecera + 3 hops, uno de ellos "* * *".
RAW_OK = """\
traceroute to 1.1.1.1 (1.1.1.1), 30 hops max, 60 byte packets
 1  gateway (192.168.1.1)  1.234 ms  1.111 ms  1.050 ms
 2  * * *
 3  one.one.one.one (1.1.1.1)  9.876 ms  9.500 ms  9.700 ms
"""

# Salida con un unico hop sin respuesta.
RAW_ALL_STARS = """\
traceroute to 10.0.0.1 (10.0.0.1), 30 hops max, 60 byte packets
 1  * * *
"""


class _FakeProc:
    """Stand-in de CompletedProcess: solo expone stdout y returncode."""

    def __init__(self, stdout: str, returncode: int = 0):
        self.stdout = stdout
        self.returncode = returncode


def _patch_run(monkeypatch, *, stdout=None, raises=None):
    """Sustituye subprocess.run en el modulo traceroute_host."""

    def fake_run(*args, **kwargs):
        if raises is not None:
            raise raises
        return _FakeProc(stdout)

    monkeypatch.setattr(tr_mod.subprocess, "run", fake_run)


def test_golden_varios_hops(monkeypatch):
    """Traceroute con varios hops: parsea numero de hop, host, IP y rtt."""
    _patch_run(monkeypatch, stdout=RAW_OK)

    result = traceroute_host("1.1.1.1")

    assert result["status"] == "ok"
    assert result["host"] == "1.1.1.1"
    assert result["raw"] == RAW_OK

    hops = result["hops"]
    assert [h["hop"] for h in hops] == [1, 2, 3]

    # Hop 1: gateway con IP y tres rtt.
    hop1 = hops[0]
    assert len(hop1["hosts"]) == 1
    assert hop1["hosts"][0]["name"] == "gateway"
    assert hop1["hosts"][0]["ip"] == "192.168.1.1"
    assert hop1["hosts"][0]["rtt_ms"] == [1.234, 1.111, 1.050]

    # Hop 2: sin respuesta -> hosts vacio.
    assert hops[1]["hosts"] == []

    # Hop 3: destino alcanzado.
    hop3 = hops[2]
    assert len(hop3["hosts"]) == 1
    assert hop3["hosts"][0]["name"] == "one.one.one.one"
    assert hop3["hosts"][0]["ip"] == "1.1.1.1"
    assert hop3["hosts"][0]["rtt_ms"] == [9.876, 9.500, 9.700]


def test_edge_hop_sin_respuesta(monkeypatch):
    """Un solo hop '* * *': hosts vacio para ese salto."""
    _patch_run(monkeypatch, stdout=RAW_ALL_STARS)

    result = traceroute_host("10.0.0.1")

    assert result["status"] == "ok"
    assert len(result["hops"]) == 1
    assert result["hops"][0]["hop"] == 1
    assert result["hops"][0]["hosts"] == []


def test_error_host_vacio(monkeypatch):
    """Host en blanco: status error sin invocar subprocess."""

    def boom(*args, **kwargs):
        raise AssertionError("subprocess.run no debe llamarse con host vacio")

    monkeypatch.setattr(tr_mod.subprocess, "run", boom)

    result = traceroute_host("   ")
    assert result["status"] == "error"
    assert "vacio" in result["error"]
