"""Monitoring stack config validation tests (T-12, issue #193).

Verifies that all monitoring config files (Prometheus, Grafana, Loki,
Promtail, AlertManager) are valid YAML and contain the expected
scrape targets, datasources, and alert rules.
"""

from __future__ import annotations

from pathlib import Path

import pytest
import yaml

MONITORING_DIR = Path(__file__).parent.parent / "monitoring"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _load_yaml(path: Path) -> dict | list:
    """Load and parse a YAML file."""
    with path.open() as f:
        return yaml.safe_load(f)


# ---------------------------------------------------------------------------
# Prometheus
# ---------------------------------------------------------------------------


class TestPrometheusConfig:
    """Validate prometheus.yml."""

    @pytest.fixture
    def config(self) -> dict:
        return _load_yaml(MONITORING_DIR / "prometheus" / "prometheus.yml")

    def test_global_scrape_interval(self, config: dict) -> None:
        assert config["global"]["scrape_interval"] == "15s"

    def test_alertmanager_target(self, config: dict) -> None:
        targets = config["alerting"]["alertmanagers"][0]["static_configs"][0]["targets"]
        assert "llmhosts-alertmanager:9093" in targets

    def test_scrape_jobs_exist(self, config: dict) -> None:
        job_names = [job["job_name"] for job in config["scrape_configs"]]
        assert "llmhosts-proxy" in job_names
        assert "llmhosts-custom" in job_names
        assert "node-exporter" in job_names
        assert "prometheus" in job_names

    def test_llmhosts_proxy_target(self, config: dict) -> None:
        proxy_job = next(j for j in config["scrape_configs"] if j["job_name"] == "llmhosts-proxy")
        targets = proxy_job["static_configs"][0]["targets"]
        assert "llmhosts:4000" in targets
        assert proxy_job["metrics_path"] == "/metrics"

    def test_llmhosts_custom_target(self, config: dict) -> None:
        custom_job = next(j for j in config["scrape_configs"] if j["job_name"] == "llmhosts-custom")
        targets = custom_job["static_configs"][0]["targets"]
        assert "llmhosts:4000" in targets
        assert custom_job["metrics_path"] == "/metrics/prometheus"


class TestAlertRules:
    """Validate alert-rules.yml."""

    @pytest.fixture
    def rules(self) -> dict:
        return _load_yaml(MONITORING_DIR / "prometheus" / "alert-rules.yml")

    def test_rule_groups_exist(self, rules: dict) -> None:
        group_names = [g["name"] for g in rules["groups"]]
        assert "system" in group_names
        assert "services" in group_names
        assert "application" in group_names
        assert "llmhosts" in group_names

    def test_critical_alerts_exist(self, rules: dict) -> None:
        all_alerts = [rule["alert"] for group in rules["groups"] for rule in group["rules"]]
        assert "DiskSpaceCritical" in all_alerts
        assert "LLMHostProxyDown" in all_alerts
        assert "NoActiveBackends" in all_alerts

    def test_all_rules_have_severity(self, rules: dict) -> None:
        for group in rules["groups"]:
            for rule in group["rules"]:
                assert "severity" in rule["labels"], f"Alert {rule['alert']} missing severity label"


# ---------------------------------------------------------------------------
# AlertManager
# ---------------------------------------------------------------------------


class TestAlertManagerConfig:
    """Validate alertmanager.yml."""

    @pytest.fixture
    def config(self) -> dict:
        return _load_yaml(MONITORING_DIR / "alertmanager" / "alertmanager.yml")

    def test_route_receiver(self, config: dict) -> None:
        assert config["route"]["receiver"] == "default-webhook"

    def test_receivers_defined(self, config: dict) -> None:
        names = [r["name"] for r in config["receivers"]]
        assert "default-webhook" in names
        assert "critical-webhook" in names

    def test_inhibit_rules(self, config: dict) -> None:
        assert len(config["inhibit_rules"]) > 0


# ---------------------------------------------------------------------------
# Loki
# ---------------------------------------------------------------------------


class TestLokiConfig:
    """Validate loki-config.yml."""

    @pytest.fixture
    def config(self) -> dict:
        return _load_yaml(MONITORING_DIR / "loki" / "loki-config.yml")

    def test_listen_port(self, config: dict) -> None:
        assert config["server"]["http_listen_port"] == 3100

    def test_retention_period(self, config: dict) -> None:
        assert config["limits_config"]["retention_period"] == "720h"

    def test_analytics_disabled(self, config: dict) -> None:
        assert config["analytics"]["reporting_enabled"] is False


# ---------------------------------------------------------------------------
# Promtail
# ---------------------------------------------------------------------------


class TestPromtailConfig:
    """Validate promtail-config.yml."""

    @pytest.fixture
    def config(self) -> dict:
        return _load_yaml(MONITORING_DIR / "promtail" / "promtail-config.yml")

    def test_loki_client_url(self, config: dict) -> None:
        assert config["clients"][0]["url"] == "http://llmhosts-loki:3100/loki/api/v1/push"

    def test_docker_scrape_job(self, config: dict) -> None:
        job_names = [j["job_name"] for j in config["scrape_configs"]]
        assert "docker" in job_names

    def test_system_scrape_job(self, config: dict) -> None:
        job_names = [j["job_name"] for j in config["scrape_configs"]]
        assert "system" in job_names


# ---------------------------------------------------------------------------
# Grafana
# ---------------------------------------------------------------------------


class TestGrafanaDatasources:
    """Validate Grafana datasource provisioning."""

    @pytest.fixture
    def config(self) -> dict:
        return _load_yaml(MONITORING_DIR / "grafana" / "provisioning" / "datasources" / "datasources.yml")

    def test_prometheus_datasource(self, config: dict) -> None:
        names = [ds["name"] for ds in config["datasources"]]
        assert "Prometheus" in names

    def test_loki_datasource(self, config: dict) -> None:
        names = [ds["name"] for ds in config["datasources"]]
        assert "Loki" in names

    def test_prometheus_is_default(self, config: dict) -> None:
        prom = next(ds for ds in config["datasources"] if ds["name"] == "Prometheus")
        assert prom["isDefault"] is True


class TestGrafanaDashboard:
    """Validate the LLMHosts overview dashboard JSON."""

    @pytest.fixture
    def dashboard(self) -> dict:
        import json

        path = MONITORING_DIR / "grafana" / "dashboards" / "llmhosts-overview.json"
        with path.open() as f:
            return json.load(f)

    def test_dashboard_has_panels(self, dashboard: dict) -> None:
        assert len(dashboard["panels"]) > 0

    def test_dashboard_uid(self, dashboard: dict) -> None:
        assert dashboard["uid"] == "llmhosts-overview"

    def test_panels_have_titles(self, dashboard: dict) -> None:
        for panel in dashboard["panels"]:
            assert panel.get("title")

    def test_key_panels_exist(self, dashboard: dict) -> None:
        titles = {p["title"] for p in dashboard["panels"]}
        assert "Request Rate" in titles
        assert "Error Rate (5xx)" in titles
        assert "Cache Hit Rate" in titles
        assert "Cost Savings (USD)" in titles
        assert "CPU Usage" in titles
        assert "Memory Usage" in titles


class TestCacheDashboard:
    """Validate the LLMHosts cache dashboard JSON."""

    @pytest.fixture
    def dashboard(self) -> dict:
        import json

        path = MONITORING_DIR / "grafana" / "dashboards" / "llmhosts-cache.json"
        with path.open() as f:
            return json.load(f)

    def test_dashboard_has_panels(self, dashboard: dict) -> None:
        assert len(dashboard["panels"]) >= 8

    def test_dashboard_uid(self, dashboard: dict) -> None:
        assert dashboard["uid"] == "llmhosts-cache"

    def test_panels_have_titles(self, dashboard: dict) -> None:
        for panel in dashboard["panels"]:
            assert panel.get("title")

    def test_key_panels_exist(self, dashboard: dict) -> None:
        titles = {p["title"] for p in dashboard["panels"]}
        assert "Cache Hit Rate (Overall)" in titles
        assert "Cache Hits vs Misses Over Time" in titles
        assert "Savings from Cache (USD)" in titles

    def test_uses_prometheus_variable(self, dashboard: dict) -> None:
        var_names = [v["name"] for v in dashboard["templating"]["list"]]
        assert "DS_PROMETHEUS" in var_names


class TestBackendsDashboard:
    """Validate the LLMHosts backends dashboard JSON."""

    @pytest.fixture
    def dashboard(self) -> dict:
        import json

        path = MONITORING_DIR / "grafana" / "dashboards" / "llmhosts-backends.json"
        with path.open() as f:
            return json.load(f)

    def test_dashboard_has_panels(self, dashboard: dict) -> None:
        assert len(dashboard["panels"]) >= 10

    def test_dashboard_uid(self, dashboard: dict) -> None:
        assert dashboard["uid"] == "llmhosts-backends"

    def test_panels_have_titles(self, dashboard: dict) -> None:
        for panel in dashboard["panels"]:
            assert panel.get("title")

    def test_key_panels_exist(self, dashboard: dict) -> None:
        titles = {p["title"] for p in dashboard["panels"]}
        assert "Backend Health Status" in titles
        assert "Requests by Backend" in titles
        assert "Cost by Backend (USD)" in titles

    def test_uses_prometheus_variable(self, dashboard: dict) -> None:
        var_names = [v["name"] for v in dashboard["templating"]["list"]]
        assert "DS_PROMETHEUS" in var_names


# ---------------------------------------------------------------------------
# Docker Compose
# ---------------------------------------------------------------------------


class TestDockerCompose:
    """Validate docker-compose.monitoring.yml."""

    @pytest.fixture
    def config(self) -> dict:
        return _load_yaml(MONITORING_DIR / "docker-compose.monitoring.yml")

    def test_required_services(self, config: dict) -> None:
        services = set(config["services"].keys())
        assert {"prometheus", "grafana", "loki", "promtail", "alertmanager", "node-exporter"} <= services

    def test_prometheus_joins_app_network(self, config: dict) -> None:
        networks = config["services"]["prometheus"]["networks"]
        assert "llmhosts-internal" in networks

    def test_all_services_have_healthcheck(self, config: dict) -> None:
        for name, svc in config["services"].items():
            assert "healthcheck" in svc, f"Service {name} missing healthcheck"

    def test_ports_bind_localhost(self, config: dict) -> None:
        for name, svc in config["services"].items():
            if "ports" in svc:
                for port in svc["ports"]:
                    assert port.startswith("127.0.0.1:"), f"Service {name} port {port} should bind to 127.0.0.1"
