"""Tests para refresh_local_hub.

No recarga Caddy ni reinicia Glance de verdad: mockea `subprocess.run`. Escribe las
configs generadas en un `tmp_path` parcheando las rutas de salida. Mockea
`discover_local_services` para devolver 2 servicios fijos (no depende de puertos reales).
"""

from __future__ import annotations

import os

import yaml
import pytest

from pipelines import refresh_local_hub as rlh_module
from pipelines.refresh_local_hub import refresh_local_hub


FAKE_SERVICES = [
    {
        "name": "metabase",
        "subdomain": "metabase",
        "port": 3030,
        "health_path": "/api/health",
        "title": "Metabase",
        "icon": "si:metabase",
        "category": "Datos",
        "up": True,
    },
    {
        "name": "portainer",
        "subdomain": "portainer",
        "port": 9000,
        "health_path": "/",
        "title": "Portainer",
        "icon": "si:portainer",
        "category": "Infra",
        "up": False,
    },
]


def _write_manifest(tmp_path) -> str:
    """Crea un manifiesto YAML mínimo y devuelve su ruta."""
    manifest = {
        "dashboard_subdomain": "home",
        "glance_port": 8585,
        "services": [],
    }
    path = os.path.join(str(tmp_path), "local_services.yaml")
    with open(path, "w", encoding="utf-8") as fh:
        yaml.safe_dump(manifest, fh)
    return path


@pytest.fixture
def patched_env(tmp_path, monkeypatch):
    """Parchea discover_local_services, las rutas de salida y subprocess.run."""
    # discover_local_services devuelve 2 servicios fijos.
    monkeypatch.setattr(rlh_module, "discover_local_services", lambda *a, **k: list(FAKE_SERVICES))

    # Rutas de salida hacia tmp_path.
    caddy_path = os.path.join(str(tmp_path), "local_hub.caddy")
    monkeypatch.setattr(rlh_module, "CADDY_FRAGMENT_PATH", caddy_path)
    monkeypatch.setattr(rlh_module, "_registry_root", lambda: str(tmp_path))

    # subprocess.run mockeado: registra las llamadas y devuelve un objeto con returncode 0.
    calls: list[list[str]] = []

    class _FakeProc:
        def __init__(self, rc: int = 0) -> None:
            self.returncode = rc
            self.stdout = ""
            self.stderr = ""

    def _fake_run(cmd, *args, **kwargs):
        calls.append(list(cmd))
        return _FakeProc(0)

    monkeypatch.setattr(rlh_module.subprocess, "run", _fake_run)

    manifest_path = _write_manifest(tmp_path)
    return {
        "manifest_path": manifest_path,
        "caddy_path": caddy_path,
        "glance_path": os.path.join(str(tmp_path), "apps", "local_hub", "glance", "glance.yml"),
        "calls": calls,
        "tmp_path": str(tmp_path),
    }


def test_compone_las_tres_funciones_y_escribe_configs(patched_env):
    """compone las tres funciones del registry y escribe ambas configs"""
    result = refresh_local_hub(manifest_path=patched_env["manifest_path"], reload=False)

    # Caddyfile escrito con un bloque por servicio + dashboard.
    assert os.path.exists(patched_env["caddy_path"])
    caddy_text = open(patched_env["caddy_path"], encoding="utf-8").read()
    assert "metabase.localhost" in caddy_text
    assert "portainer.localhost" in caddy_text
    assert "home.localhost" in caddy_text  # bloque del dashboard
    assert "reverse_proxy 127.0.0.1:3030" in caddy_text

    # Glance escrito con el bloque servidor fijo + la salida de render_glance_config.
    assert os.path.exists(result["glance_path"])
    glance_text = open(result["glance_path"], encoding="utf-8").read()
    assert "host: 127.0.0.1" in glance_text
    assert "port: 8585" in glance_text
    assert "primary-color: 210 90 70" in glance_text
    assert "type: monitor" in glance_text


def test_glance_full_es_yaml_parseable(patched_env):
    """la config completa de Glance generada es YAML parseable"""
    result = refresh_local_hub(manifest_path=patched_env["manifest_path"], reload=False)

    glance_text = open(result["glance_path"], encoding="utf-8").read()
    parsed = yaml.safe_load(glance_text)
    assert isinstance(parsed, dict)
    assert parsed["server"]["host"] == "127.0.0.1"
    assert parsed["server"]["port"] == 8585
    assert "theme" in parsed
    assert "pages" in parsed  # la salida de render_glance_config


def test_reload_false_no_llama_subprocess(patched_env):
    """con reload=False no se llama a subprocess.run"""
    result = refresh_local_hub(manifest_path=patched_env["manifest_path"], reload=False)

    assert patched_env["calls"] == []
    assert result["reloaded"] is False
    assert result["caddy_reload_rc"] is None
    assert result["glance_restart_rc"] is None


def test_reload_true_recarga_caddy_y_reinicia_glance(patched_env):
    """con reload=True se invoca caddy reload y systemctl --user restart glance"""
    result = refresh_local_hub(manifest_path=patched_env["manifest_path"], reload=True)

    cmds = patched_env["calls"]
    assert len(cmds) == 2
    assert cmds[0][0] == "caddy" and "reload" in cmds[0]
    assert cmds[1] == ["systemctl", "--user", "restart", "glance"]
    assert result["reloaded"] is True
    assert result["caddy_reload_rc"] == 0
    assert result["glance_restart_rc"] == 0


def test_dict_retorno_tiene_claves_esperadas(patched_env):
    """el dict de retorno tiene todas las claves del contrato"""
    result = refresh_local_hub(manifest_path=patched_env["manifest_path"], reload=False)

    expected_keys = {
        "total", "up", "down", "caddy_path", "glance_path",
        "reloaded", "caddy_reload_rc", "glance_restart_rc", "services",
    }
    assert expected_keys <= set(result.keys())
    assert result["total"] == 2
    assert result["up"] == 1
    assert result["down"] == 1
    assert len(result["services"]) == 2
    assert result["services"][0]["subdomain"] == "metabase"
    assert {"name", "subdomain", "port", "up"} <= set(result["services"][0].keys())


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
