#!/usr/bin/python3
# Returns current "live issues" for the map page ticker.
# Reads from the status API — single source of truth for pod connectivity
# (sites.<site>.generator_groups.<group>.pods.<pod>.connectivity) and gen status.
# Output: {"issues": [{type, severity, site, label, since, duration_min}, ...]}

import json
import sys
import urllib.request
from datetime import datetime, timezone

sys.path.insert(0, '/opt/ngon/apps')
from managers.auth_manager import AuthManager

STATUS_URL = 'http://localhost:5050/api/status'

# Planned-out maintenance statuses — don't alert on these
PLANNED_OUT = {'oos', 'swap', 'inspect', 'parts', 'loaner', 'gas'}

MAX_PER_TYPE = 15


def parse_ts(s):
    if not s:
        return None
    try:
        return datetime.fromisoformat(s.replace('Z', '+00:00'))
    except Exception:
        return None


def now_utc():
    return datetime.now(timezone.utc)


def duration_min(ts):
    if not ts:
        return None
    if ts.tzinfo is None:
        ts = ts.replace(tzinfo=timezone.utc)
    delta = now_utc() - ts
    return max(0, int(delta.total_seconds() // 60))


def load_status():
    try:
        with urllib.request.urlopen(STATUS_URL, timeout=5) as r:
            return json.loads(r.read().decode())
    except Exception:
        return {}


def main():
    import cgi
    form = cgi.FieldStorage()
    auth = AuthManager('map')
    auth_required, should_exit, headers = auth.require_auth(form)
    if auth_required or should_exit:
        print("Content-Type: application/json")
        if headers:
            print(headers)
        print("")
        print(json.dumps({"error": "unauthorized"}))
        return

    status = load_status()
    issues = []

    for site_name, site in (status.get('sites') or {}).items():
        for group in (site.get('generator_groups') or {}).values():
            # --- Pods offline ---
            # Match map_core.js aggregation: a pod is "down" when it has
            # miners configured but none are hashing. connectivity.state is
            # not reliably populated on the status API today.
            for pod_name, pod in (group.get('pods') or {}).items():
                stats = pod.get('stats') or {}
                total = stats.get('total_miners') or 0
                hashing = stats.get('miners_hashing') or 0
                if total <= 0 or hashing > 0:
                    continue
                conn = pod.get('connectivity') or {}
                ts = parse_ts(conn.get('since'))
                issues.append({
                    'type': 'pod_down',
                    'severity': 'red',
                    'site': site_name,
                    'label': f'{site_name} / {pod_name} offline',
                    'since': conn.get('since'),
                    'duration_min': duration_min(ts),
                })

            # --- Gens down ---
            for gen_id, gen in (group.get('generators') or {}).items():
                if gen.get('status') == 'Running':
                    continue
                mstatus = (gen.get('maintenance_status') or '').lower()
                if mstatus in PLANNED_OUT:
                    continue
                # Prefer gen.outage.since (our source of truth), fall back to
                # provider data / status change for gens not yet published.
                outage = gen.get('outage') or {}
                stats = gen.get('stats') or {}
                ts_s = (outage.get('since')
                        or stats.get('run_status_timestamp')
                        or gen.get('last_status_change'))
                ts = parse_ts(ts_s)
                raw_status = gen.get('status') or 'Down'
                short_status = raw_status if len(raw_status) < 28 else raw_status[:25] + '…'
                issues.append({
                    'type': 'gen_down',
                    'severity': 'red',
                    'site': site_name,
                    'label': f'{site_name} / {gen_id} {short_status}',
                    'since': ts.isoformat() if ts else None,
                    'duration_min': duration_min(ts),
                })

    sev_rank = {'red': 0, 'yellow': 1, 'grey': 2}
    def sort_key(i):
        return (sev_rank.get(i['severity'], 9), -(i['duration_min'] or 0))

    pods  = sorted([i for i in issues if i['type'] == 'pod_down'], key=sort_key)
    gens  = sorted([i for i in issues if i['type'] == 'gen_down'], key=sort_key)
    total_pods = len(pods)
    total_gens = len(gens)
    pods = pods[:MAX_PER_TYPE]
    gens = gens[:MAX_PER_TYPE]

    print("Content-Type: application/json\n")
    print(json.dumps({
        'issues': pods + gens,
        'total': total_pods + total_gens,
        'shown': len(pods) + len(gens),
        'total_by_type': {'pod_down': total_pods, 'gen_down': total_gens},
        'shown_by_type': {'pod_down': len(pods),  'gen_down': len(gens)},
        'generated_at': now_utc().isoformat(),
    }))


if __name__ == '__main__':
    main()
