#!/usr/bin/env python3
"""Build and push the Ahel Overview Grafana dashboard."""
import json
import urllib.request

import os

GRAFANA = os.environ.get("GRAFANA_URL", "http://localhost:3000")
AUTH = os.environ.get("GRAFANA_AUTH")  # "user:pass" or None for anonymous Grafana
DS = {"type": "prometheus", "uid": "P3BD1A695A0E449AB"}

panels = []
_id = 0


def nid():
    global _id
    _id += 1
    return _id


def target(expr, legend=None, instant=False):
    t = {"expr": expr, "refId": "A", "datasource": DS}
    if legend:
        t["legendFormat"] = legend
    if instant:
        t["instant"] = True
    return t


def up_stat(title, expr, x, w=3, y=0, h=3):
    return {
        "id": nid(), "type": "stat", "title": title, "datasource": DS,
        "gridPos": {"x": x, "y": y, "w": w, "h": h},
        "targets": [target(expr, instant=True)],
        "fieldConfig": {"defaults": {
            "mappings": [{"type": "value", "options": {
                "1": {"text": "UP", "color": "green"},
                "0": {"text": "DOWN", "color": "red"}}}],
            "thresholds": {"mode": "absolute", "steps": [
                {"color": "red", "value": None}, {"color": "green", "value": 1}]},
            "noValue": "DOWN", "color": {"mode": "thresholds"},
        }, "overrides": []},
        "options": {"colorMode": "background", "graphMode": "none",
                    "textMode": "value", "reduceOptions": {"calcs": ["lastNotNull"]}},
    }


def stat(title, expr, x, y, w=4, h=4, unit="none", decimals=1, sparkline=True,
         thresholds=None, mappings=None, legend=None):
    steps = thresholds or [{"color": "blue", "value": None}]
    return {
        "id": nid(), "type": "stat", "title": title, "datasource": DS,
        "gridPos": {"x": x, "y": y, "w": w, "h": h},
        "targets": [target(expr, legend=legend)],
        "fieldConfig": {"defaults": {
            "unit": unit, "decimals": decimals, "mappings": mappings or [],
            "thresholds": {"mode": "absolute", "steps": steps},
            "color": {"mode": "thresholds"}, "noValue": "0",
        }, "overrides": []},
        "options": {"colorMode": "value",
                    "graphMode": "area" if sparkline else "none",
                    "reduceOptions": {"calcs": ["lastNotNull"]}},
    }


def timeseries(title, targets_, x, y, w=12, h=8, unit="none", time_from=None,
               stack=False):
    for i, t in enumerate(targets_):
        t["refId"] = chr(ord("A") + i)
    p = {
        "id": nid(), "type": "timeseries", "title": title, "datasource": DS,
        "gridPos": {"x": x, "y": y, "w": w, "h": h},
        "targets": targets_,
        "fieldConfig": {"defaults": {
            "unit": unit,
            "custom": {"drawStyle": "line", "lineWidth": 2, "fillOpacity": 12,
                       "showPoints": "never", "spanNulls": True,
                       "stacking": {"mode": "normal" if stack else "none"}},
        }, "overrides": []},
        "options": {"legend": {"displayMode": "list", "placement": "bottom"},
                    "tooltip": {"mode": "multi"}},
    }
    if time_from:
        p["timeFrom"] = time_from
    return p


def table(title, expr, x, y, w=12, h=6):
    t = target(expr, instant=True)
    t["format"] = "table"
    return {
        "id": nid(), "type": "table", "title": title, "datasource": DS,
        "gridPos": {"x": x, "y": y, "w": w, "h": h},
        "targets": [t],
        "transformations": [
            {"id": "labelsToFields", "options": {}},
            {"id": "organize", "options": {"excludeByName": {
                "Time": True, "__name__": True, "job": True, "Value": True}}},
        ],
        "fieldConfig": {"defaults": {"custom": {"align": "left"}}, "overrides": []},
        "options": {"showHeader": True},
    }


def row(title, y):
    return {"id": nid(), "type": "row", "title": title, "collapsed": False,
            "gridPos": {"x": 0, "y": y, "w": 24, "h": 1}, "panels": []}


# ── Row: fleet status ────────────────────────────────────────────────
panels.append(row("Fleet", 0))
ups = [
    ("LLM Pool", 'up{job="ahel-pool"}'),
    ("Bifrost", 'up{job="bifrost-gateway"}'),
    ("m4a", 'up{instance="192.168.8.178:9101"}'),
    ("m4b", 'up{instance="192.168.8.152:9101"}'),
    ("Pi", 'up{job="netdata-pi"}'),
    ("Qdrant", 'up{job="qdrant"}'),
    ("Embed", 'up{job="embed-swap"}'),
    ("Prometheus", 'up{job="prometheus"}'),
]
for i, (t, e) in enumerate(ups):
    panels.append(up_stat(t, e, x=i * 3, y=1))

# ── Row: LLM pool ────────────────────────────────────────────────────
panels.append(row("LLM Pool (m4a + m4b, TB4)", 4))
panels.append(stat("Gen tok/s", "llamacpp:predicted_tokens_seconds", 0, 5,
                   thresholds=[{"color": "orange", "value": None},
                               {"color": "green", "value": 15}]))
panels.append(stat("Prompt tok/s", "llamacpp:prompt_tokens_seconds", 4, 5))
panels.append(stat("In flight", "llamacpp:requests_processing", 8, 5, decimals=0,
                   thresholds=[{"color": "green", "value": None},
                               {"color": "orange", "value": 1}]))
panels.append(stat("Queued", "llamacpp:requests_deferred", 12, 5, decimals=0,
                   thresholds=[{"color": "green", "value": None},
                               {"color": "red", "value": 1}]))
panels.append(stat("Tokens 24h", "increase(llamacpp:tokens_predicted_total[24h])",
                   16, 5, decimals=0, sparkline=False))
panels.append(stat("Thermal pressure (max)", "max(erebor_thermal_pressure)", 20, 5,
                   decimals=0, sparkline=False,
                   thresholds=[{"color": "green", "value": None},
                               {"color": "orange", "value": 1},
                               {"color": "red", "value": 2}],
                   mappings=[{"type": "value", "options": {
                       "0": {"text": "Nominal", "color": "green"}}}]))
panels.append(timeseries("Token throughput (2m rate)", [
    target("rate(llamacpp:tokens_predicted_total[2m])", "generated tok/s"),
    target("rate(llamacpp:prompt_tokens_total[2m])", "prompt tok/s"),
], 0, 9, w=24, h=7))

# ── Row: gateway ─────────────────────────────────────────────────────
panels.append(row("Bifrost Gateway (m1)", 16))
panels.append(stat("Active reqs", "sum(bifrost_active_requests)", 0, 17,
                   decimals=0, w=4))
panels.append(stat("Requests 24h",
                   "sum(increase(bifrost_success_requests_total[24h]))", 4, 17,
                   decimals=0))
panels.append(stat("Errors 24h",
                   "sum(increase(bifrost_error_requests_total[24h])) or vector(0)",
                   8, 17, decimals=0,
                   thresholds=[{"color": "green", "value": None},
                               {"color": "red", "value": 1}]))
panels.append(stat("Out tok/min", "sum(rate(bifrost_output_tokens_total[15m]))*60",
                   12, 17, decimals=0))
panels.append(stat("P95 latency (24h)",
                   "histogram_quantile(0.95, sum by (le) "
                   "(increase(bifrost_upstream_latency_seconds_bucket[24h])))",
                   16, 17, unit="s", w=8, sparkline=False))
panels.append(timeseries("Requests by provider (req/min)", [
    target("sum by (provider) (rate(bifrost_upstream_requests_total[15m]))*60",
           "{{provider}}"),
], 0, 21, w=12, h=7))
panels.append(timeseries("Output tokens by provider (tok/min)", [
    target("sum by (provider) (rate(bifrost_output_tokens_total[15m]))*60",
           "{{provider}}"),
], 12, 21, w=12, h=7))

# ── Row: nodes ───────────────────────────────────────────────────────
panels.append(row("Nodes", 28))
panels.append(timeseries("M4 GPU power", [
    target("erebor_gpu_power_mw", "{{node}} GPU"),
    target("erebor_cpu_power_mw", "{{node}} CPU"),
], 0, 29, w=8, h=7, unit="mwatt"))
panels.append(timeseries("Memory used %", [
    target("erebor_mem_used_pct", "{{node}}"),
    target('100 * sum(netdata_system_ram_MiB_average{dimension="used"}) '
           "/ sum(netdata_system_ram_MiB_average)", "pi"),
], 8, 29, w=8, h=7, unit="percent"))
panels.append(timeseries("Load (1m)", [
    target("erebor_node_load1", "{{node}}"),
    target('netdata_system_load_load_average{dimension="load1"}', "pi"),
], 16, 29, w=8, h=7))

# ── Row: WAN + aux ───────────────────────────────────────────────────
panels.append(row("WAN + Aux (m4b)", 36))
panels.append(stat("WAN down", "speedtest_download_bits_per_second/1e6", 0, 37,
                   decimals=0, unit="none", sparkline=False))
panels.append(stat("WAN up", "speedtest_upload_bits_per_second/1e6", 4, 37,
                   decimals=0, sparkline=False))
panels.append(stat("WAN ping", "speedtest_ping_latency_milliseconds", 8, 37,
                   unit="ms", decimals=0, sparkline=False))
panels.append(stat("Qdrant collections", "collections_total", 12, 37, decimals=0,
                   sparkline=False))
panels.append(stat("Qdrant vectors", "collections_vector_total", 16, 37,
                   decimals=0, sparkline=False))
panels.append(stat("Embed CPU %", "avg(llamaswap_cpu_util_percent)", 20, 37,
                   unit="percent"))
panels.append(timeseries("WAN speedtest (Mbit/s)", [
    target("speedtest_download_bits_per_second/1e6", "down"),
    target("speedtest_upload_bits_per_second/1e6", "up"),
], 0, 41, w=24, h=6, time_from="24h"))

# ── Row: thermal & power ─────────────────────────────────────────────
panels.append(row("Thermal & Power", 47))
panels.append(stat("Thermal pressure", "erebor_thermal_pressure", 0, 48,
                   w=6, h=6, decimals=0, sparkline=False, legend="{{node}}",
                   thresholds=[{"color": "green", "value": None},
                               {"color": "orange", "value": 1},
                               {"color": "red", "value": 2}],
                   mappings=[{"type": "value", "options": {
                       "0": {"text": "Nominal", "color": "green"},
                       "1": {"text": "Fair", "color": "orange"},
                       "2": {"text": "Serious", "color": "red"},
                       "3": {"text": "Critical", "color": "red"}}}]))
panels.append(stat("NAS temp", "nas_temp_celsius", 6, 48, w=6, h=6,
                   unit="celsius", decimals=1, sparkline=True,
                   thresholds=[{"color": "green", "value": None},
                               {"color": "orange", "value": 75},
                               {"color": "red", "value": 85}]))
panels.append(timeseries("Node power (CPU+GPU)", [
    target("(erebor_cpu_power_mw + erebor_gpu_power_mw) / 1000", "{{node}}"),
], 12, 48, w=12, h=6, unit="watt"))

# ── Row: alerts ──────────────────────────────────────────────────────
panels.append(row("Alerts", 54))
panels.append(stat("Firing alerts", 'count(ALERTS{alertstate="firing"}) or vector(0)',
                   0, 55, w=4, h=6, decimals=0, sparkline=False,
                   thresholds=[{"color": "green", "value": None},
                               {"color": "red", "value": 1}]))
panels.append(table("Firing alerts", 'ALERTS{alertstate="firing"}', 4, 55,
                    w=20, h=6))

dashboard = {
    "uid": "ahel-overview",
    "title": "Ahel Overview",
    "tags": ["ahel", "fleet"],
    "timezone": "browser",
    "refresh": "5s",
    "time": {"from": "now-30m", "to": "now"},
    "editable": True,
    "graphTooltip": 1,
    "panels": panels,
    "schemaVersion": 39,
}

body = json.dumps({"dashboard": dashboard, "overwrite": True,
                   "message": "Ahel Overview built by Claude"}).encode()

import base64
headers = {"Content-Type": "application/json"}
if AUTH:
    headers["Authorization"] = "Basic " + base64.b64encode(AUTH.encode()).decode()

for path, payload in (
    ("/api/dashboards/db", body),
    ("/api/org/preferences",
     json.dumps({"homeDashboardUID": "ahel-overview"}).encode()),
):
    req = urllib.request.Request(
        GRAFANA + path, data=payload, method="PUT" if "preferences" in path else "POST",
        headers=headers)
    with urllib.request.urlopen(req, timeout=15) as resp:
        print(path, resp.status, resp.read()[:200].decode())
