"""refresh_local_hub — orquesta el refresco del sistema local_hub.

Pipeline impuro del dominio `pipelines`. Compone tres funciones del registry para
regenerar la infraestructura que expone los servicios locales como subdominios
`*.localhost`:

  1. discover_local_services_py_infra — descubre y normaliza los servicios (manifiesto
     + servicios del registry con bloque `service:`), comprobando estado up/down.
  2. render_caddyfile_py_infra        — genera el fragmento de Caddyfile.
  3. render_glance_config_py_infra    — genera la config del widget monitor de Glance.

Después escribe el fragmento de Caddyfile en /etc/caddy/conf.d/local_hub.caddy (el
usuario tiene ACL de escritura ahí, sin sudo) y la config de Glance en
apps/local_hub/glance/glance.yml. Si `reload=True`, recarga Caddy (admin API en
localhost:2019, sin sudo) y reinicia la user-unit `glance` (sin sudo).

Pensado para correrse a diario desde dag_engine con un step `function:`, o a mano:
`fn run refresh_local_hub`.
"""

from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
from typing import Any

import yaml

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
sys.path.insert(0, os.path.join(ROOT, "python", "functions"))

from infra.discover_local_services import discover_local_services  # noqa: E402
from infra.render_caddyfile import render_caddyfile  # noqa: E402
from infra.render_glance_config import render_glance_config  # noqa: E402

# Ruta del fragmento de Caddyfile. El usuario tiene ACL de escritura aquí (sin sudo).
CADDY_FRAGMENT_PATH = "/etc/caddy/conf.d/local_hub.caddy"

# Bloque fijo de servidor + tema que precede a la salida de render_glance_config.
GLANCE_SERVER_BLOCK = (
    "server:\n"
    "  host: 127.0.0.1\n"
    "  port: 8585\n"
    "\n"
    "theme:\n"
    "  background-color: 240 8 9\n"
    "  contrast-multiplier: 1.2\n"
    "  primary-color: 210 90 70\n"
    "\n"
)


def _default_manifest_path() -> str:
    """Ruta por defecto del manifiesto, derivada de la raíz del registry."""
    root = os.environ.get("FN_REGISTRY_ROOT") or ROOT
    return os.path.join(root, "apps", "local_hub", "local_services.yaml")


def _registry_root() -> str:
    """Raíz del registry: FN_REGISTRY_ROOT si está, si no la derivada del path del módulo."""
    return os.environ.get("FN_REGISTRY_ROOT") or ROOT


def refresh_local_hub(
    manifest_path: str | None = None,
    reload: bool = True,
) -> dict[str, Any]:
    """Refresca el sistema local_hub: descubre servicios, regenera configs y recarga.

    Args:
        manifest_path: ruta al manifiesto YAML del local_hub. Si es None, se usa
            ``<RAIZ>/apps/local_hub/local_services.yaml`` (RAIZ derivada de
            FN_REGISTRY_ROOT o del path del propio módulo).
        reload: si True, recarga Caddy (admin API en localhost:2019) y reinicia la
            user-unit ``glance``. Si False, solo escribe las configs y no toca
            ningún servicio.

    Returns:
        dict resumen con las claves: total, up, down, caddy_path, glance_path,
        reloaded, caddy_reload_rc, glance_restart_rc, services.

    Raises:
        RuntimeError: si la config de Glance generada no es YAML parseable.
    """
    if manifest_path is None:
        manifest_path = _default_manifest_path()

    # 1. Lee el manifiesto para extraer dashboard_subdomain y glance_port.
    try:
        with open(manifest_path, "r", encoding="utf-8") as fh:
            manifest = yaml.safe_load(fh) or {}
    except (OSError, yaml.YAMLError) as exc:
        raise RuntimeError(
            f"refresh_local_hub: no se puede leer el manifiesto {manifest_path}: {exc}"
        ) from exc

    dashboard_subdomain = manifest.get("dashboard_subdomain") or "home"
    glance_port = manifest.get("glance_port") or 8585

    # 2. Descubre los servicios (manifiesto + registry).
    services = discover_local_services(manifest_path, include_registry=True)

    # 3. Bloque del dashboard para el Caddyfile.
    dashboard = {"subdomain": dashboard_subdomain, "port": glance_port}

    # 4. Renderiza el fragmento de Caddyfile.
    caddy_text = render_caddyfile(services, dashboard)

    # 5. Construye la config completa de Glance (bloque fijo + render_glance_config).
    glance_full = GLANCE_SERVER_BLOCK + render_glance_config(services)

    # Verifica que el YAML resultante es parseable antes de escribir nada.
    try:
        yaml.safe_load(glance_full)
    except yaml.YAMLError as exc:
        raise RuntimeError(
            f"refresh_local_hub: la config de Glance generada no es YAML válido: {exc}"
        ) from exc

    # 6. Escribe el fragmento de Caddyfile (ACL del usuario, sin sudo).
    with open(CADDY_FRAGMENT_PATH, "w", encoding="utf-8") as fh:
        fh.write(caddy_text)

    # 7. Escribe la config de Glance (crea el dir si falta).
    glance_path = os.path.join(_registry_root(), "apps", "local_hub", "glance", "glance.yml")
    os.makedirs(os.path.dirname(glance_path), exist_ok=True)
    with open(glance_path, "w", encoding="utf-8") as fh:
        fh.write(glance_full)

    up = sum(1 for s in services if s.get("up"))
    down = len(services) - up

    caddy_reload_rc: int | None = None
    glance_restart_rc: int | None = None

    # 8. Recarga Caddy y reinicia Glance (ambos sin sudo).
    if reload:
        try:
            caddy_proc = subprocess.run(
                ["caddy", "reload", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"],
                capture_output=True,
                text=True,
                timeout=30,
            )
            caddy_reload_rc = caddy_proc.returncode
        except (OSError, subprocess.SubprocessError) as exc:
            caddy_reload_rc = -1
            sys.stderr.write(f"refresh_local_hub: fallo recargando Caddy: {exc}\n")

        try:
            glance_proc = subprocess.run(
                ["systemctl", "--user", "restart", "glance"],
                capture_output=True,
                text=True,
                timeout=30,
            )
            glance_restart_rc = glance_proc.returncode
        except (OSError, subprocess.SubprocessError) as exc:
            glance_restart_rc = -1
            sys.stderr.write(f"refresh_local_hub: fallo reiniciando Glance: {exc}\n")

    return {
        "total": len(services),
        "up": up,
        "down": down,
        "caddy_path": CADDY_FRAGMENT_PATH,
        "glance_path": glance_path,
        "reloaded": reload,
        "caddy_reload_rc": caddy_reload_rc,
        "glance_restart_rc": glance_restart_rc,
        "services": [
            {
                "name": s.get("name"),
                "subdomain": s.get("subdomain"),
                "port": s.get("port"),
                "up": s.get("up"),
            }
            for s in services
        ],
    }


def main() -> None:
    parser = argparse.ArgumentParser(description="Refresca el sistema local_hub.")
    parser.add_argument(
        "--manifest-path",
        default=None,
        help="Ruta al manifiesto YAML del local_hub (default: <RAIZ>/apps/local_hub/local_services.yaml).",
    )
    parser.add_argument(
        "--no-reload",
        action="store_true",
        help="No recargar Caddy ni reiniciar Glance; solo regenerar las configs.",
    )
    args = parser.parse_args()

    result = refresh_local_hub(
        manifest_path=args.manifest_path,
        reload=not args.no_reload,
    )
    print(json.dumps(result, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()
