#!/usr/bin/env python3
"""
Monitoring Stack Validation Script

Validates that all Prometheus targets are UP, key metrics exist for
each dashboard, and Grafana dashboard variables are properly configured.

Transfer to CT 141 via base64 (pitfall 40):
  SCRIPT=$(base64 -w0 monitoring_validation.py)
  ssh -i ~/.ssh/key root@10.0.20.70 "echo '$SCRIPT' | base64 -d | pct exec 141 -- tee /tmp/validate.py >/dev/null && pct exec 141 -- python3 /tmp/validate.py"

Usage: python3 monitoring_validation.py
Exit: 0 if all checks pass, 1 if any target down or metric missing
"""

import json
import sys
import urllib.request
import urllib.parse
import base64

PROM = "http://127.0.0.1:9090"
GRAFANA = "http://127.0.0.1:3000"
GRAFANA_USER = "admin"
GRAFANA_PASS = "Grafana2026!"
AUTH = base64.b64encode(f"{GRAFANA_USER}:{GRAFANA_PASS}".encode()).decode()


def prom_query(expr):
    url = f"{PROM}/api/v1/query?query={urllib.parse.quote(expr)}"
    with urllib.request.urlopen(url) as r:
        d = json.loads(r.read())
    return d.get("data", {}).get("result", [])


def grafana_get(path):
    req = urllib.request.Request(f"{GRAFANA}{path}")
    req.add_header("Authorization", f"Basic {AUTH}")
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())


def grafana_post(path, data):
    body = json.dumps(data).encode()
    req = urllib.request.Request(f"{GRAFANA}{path}", data=body, method="POST")
    req.add_header("Authorization", f"Basic {AUTH}")
    req.add_header("Content-Type", "application/json")
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())


def fix_datasource_var(dash, var_name, prom_uid):
    """Fix a datasource template variable with empty current value."""
    templating = dash.get("templating", {}).get("list", [])
    changed = False
    for v in templating:
        if v.get("name") == var_name and not v.get("current"):
            v["current"] = {"text": "Prometheus", "value": prom_uid}
            changed = True
    return changed


def add_datasource_var(dash, var_name, prom_uid):
    """Add a missing datasource template variable."""
    templating = dash.get("templating", {}).get("list", [])
    existing = [v.get("name") for v in templating]
    if var_name not in existing:
        templating.insert(0, {
            "name": var_name,
            "label": "Prometheus",
            "type": "datasource",
            "query": "prometheus",
            "current": {"text": "Prometheus", "value": prom_uid},
            "includeAll": False,
            "hide": 0,
        })
        return True
    return False


def main():
    errors = []

    print("=" * 60)
    print("MONITORING STACK VALIDATION")
    print("=" * 60)

    # 1. Check all Prometheus targets
    with urllib.request.urlopen(f"{PROM}/api/v1/targets") as r:
        d = json.loads(r.read())
    targets = d.get("data", {}).get("activeTargets", [])
    up = [t for t in targets if t["health"] == "up"]
    down = [t for t in targets if t["health"] != "up"]

    print(f"\n1. Prometheus Targets: {len(up)} UP, {len(down)} DOWN")
    for t in down:
        job = t.get("labels", {}).get("job", "?")
        inst = t.get("scrapeUrl", "?")
        err = t.get("lastError", "")[:60]
        print(f"   DOWN: {job} {inst} — {err}")
        errors.append(f"Target down: {job}/{inst}")

    # 2. Check key metrics per dashboard
    checks = [
        ("Node Exporter", "node_uname_info", 1),
        ("PVE", "pve_cpu_usage_ratio", 1),
        ("Ceph", "ceph_health_status", 1),
        ("MySQL/Galera", "mysql_up", 1),
    ]
    print("\n2. Metrics per dashboard:")
    for name, metric, min_series in checks:
        r = prom_query(metric)
        count = len(r)
        status = "OK" if count >= min_series else "FAIL"
        print(f"   [{status}] {name:20s} {metric:30s} {count:4d} series")
        if count < min_series:
            errors.append(f"No data for {metric}")

    # 3. Get Prometheus datasource UID
    ds_list = grafana_get("/api/datasources")
    prom_uid = None
    for ds in ds_list:
        if ds.get("type") == "prometheus":
            prom_uid = ds.get("uid")
            break
    if not prom_uid:
        print("\n3. ERROR: No Prometheus datasource found in Grafana!")
        errors.append("No Prometheus datasource")
        prom_uid = "PBFA97CFB590B2093"  # fallback from memory

    # 4. Check and fix dashboard variables
    dashboards = {
        "Node Exporter Full": {"uid": "rYdddlPWk", "ds_var": "ds_prometheus"},
        "PVE via Prometheus": {"uid": "Dp7Cd57Zza", "ds_var": "DS_PROMETHEUS"},
        "Ceph Cluster": {"uid": "tbO9LAiZK", "ds_var": "DS_PROMETHEUS"},
        "MySQL Overview": {"uid": "MQWgroiiz", "ds_var": None},
    }

    print("\n3. Dashboard variables:")
    for name, info in dashboards.items():
        uid = info["uid"]
        ds_var = info["ds_var"]
        d = grafana_get(f"/api/dashboards/uid/{uid}")
        dash = d.get("dashboard", {})
        panels = dash.get("panels", [])
        templating = dash.get("templating", {}).get("list", [])

        vars_str = ", ".join(
            f"${v['name']}={v.get('current', {}).get('value', '?')}"
            for v in templating
        )
        print(f"   {name:25s} {len(panels):3d} panels | {vars_str}")

        # Fix missing/empty datasource variables
        if ds_var:
            needs_fix = add_datasource_var(dash, ds_var, prom_uid)
            needs_fix = fix_datasource_var(dash, ds_var, prom_uid) or needs_fix
            if needs_fix:
                result = grafana_post("/api/dashboards/db", {
                    "dashboard": dash,
                    "message": f"Fix {ds_var} datasource variable",
                    "overwrite": True,
                })
                print(f"   → Fixed: {result.get('status')}")

    # 5. Alertmanager
    print("\n4. Alertmanager:")
    try:
        with urllib.request.urlopen("http://127.0.0.1:9093/api/v2/alerts") as r:
            alerts = json.loads(r.read())
        print(f"   Active alerts: {len(alerts)}")
        for a in alerts[:5]:
            lbl = a.get("labels", {}).get("alertname", "?")
            summary = a.get("annotations", {}).get("summary", "")[:60]
            print(f"   - {lbl}: {summary}")
    except Exception as e:
        print(f"   Error: {e}")
        errors.append(f"Alertmanager: {e}")

    # 6. Summary
    print("\n" + "=" * 60)
    if errors:
        print(f"RESULT: {len(errors)} ISSUES FOUND")
        for e in errors:
            print(f"  ! {e}")
        sys.exit(1)
    else:
        print("RESULT: ALL CHECKS PASSED")
        sys.exit(0)


if __name__ == "__main__":
    main()
