#!/usr/bin/env python3
"""Morning digest: summarise yesterday's RCAs and post to Slack.

Run from cron at 09:00 local, or as a scheduled GitHub Action.

Environment:
  EXLOGARE_TOKEN      Bearer token with scope=read (exl_…). Required.
  EXLOGARE_API_URL    Defaults to https://api.exlogare.net.
  SLACK_WEBHOOK_URL   Slack incoming-webhook URL. If unset, prints to stdout.
  SEVERITY            Lowest severity to include (low/medium/high). Default: medium.

Standard library only — no extra dependencies, copy-paste-runnable.
"""
from __future__ import annotations

import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timedelta, timezone

API_BASE = os.environ.get("EXLOGARE_API_URL", "https://api.exlogare.net").rstrip("/")
TOKEN = os.environ.get("EXLOGARE_TOKEN")
SLACK_WEBHOOK = os.environ.get("SLACK_WEBHOOK_URL")
MIN_SEVERITY = os.environ.get("SEVERITY", "medium").lower()

SEV_RANK = {"low": 0, "medium": 1, "high": 2}


def _get(path: str) -> dict:
    url = f"{API_BASE}{path}"
    req = urllib.request.Request(url, headers={"Authorization": f"Bearer {TOKEN}"})
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read().decode("utf-8"))


def fetch_yesterday_analyses() -> list[dict]:
    """Walk every page so a backlog never gets silently dropped."""
    now = datetime.now(tz=timezone.utc)
    start = (now - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
    end = start + timedelta(days=1)
    items: list[dict] = []
    cursor: str | None = None
    while True:
        params = {
            "since": start.isoformat(),
            "until": end.isoformat(),
            "limit": "200",
        }
        if cursor:
            params["cursor"] = cursor
        page = _get("/api/v1/analyses?" + urllib.parse.urlencode(params))
        items.extend(page.get("items", []))
        cursor = page.get("next_cursor")
        if not cursor:
            break
    min_rank = SEV_RANK.get(MIN_SEVERITY, 1)
    return [a for a in items if SEV_RANK.get(a.get("severity", "low"), 0) >= min_rank]


def build_summary(items: list[dict]) -> str:
    if not items:
        return ":white_check_mark: No CI failures yesterday."
    lines = [
        f":exclamation: *{len(items)} CI failures yesterday* (severity >= {MIN_SEVERITY})",
        "",
    ]
    by_proj: dict[str, list[dict]] = {}
    for a in items:
        key = a.get("project_path") or a.get("project_id") or "(unknown)"
        by_proj.setdefault(key, []).append(a)
    for proj, rows in sorted(by_proj.items(), key=lambda kv: -len(kv[1])):
        lines.append(f"*{proj}* — {len(rows)} failure(s)")
        for r in rows[:3]:
            sev = r.get("severity", "?")
            cause = (r.get("root_cause") or "").splitlines()[0][:120]
            url = r.get("pipeline_url") or r.get("job_url") or ""
            lines.append(f"  • [{sev}] {cause}  {url}")
        if len(rows) > 3:
            lines.append(f"  • …and {len(rows) - 3} more")
        lines.append("")
    return "\n".join(lines).rstrip()


def post_slack(text: str) -> None:
    if not SLACK_WEBHOOK:
        print(text)
        return
    body = json.dumps({"text": text}).encode("utf-8")
    req = urllib.request.Request(
        SLACK_WEBHOOK,
        data=body,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=15) as resp:
        if resp.status >= 300:
            raise RuntimeError(f"Slack POST failed: {resp.status}")


def main() -> int:
    if not TOKEN:
        print("EXLOGARE_TOKEN env var is required", file=sys.stderr)
        return 2
    try:
        items = fetch_yesterday_analyses()
    except urllib.error.HTTPError as exc:
        print(f"API error {exc.code}: {exc.read().decode('utf-8', 'replace')}", file=sys.stderr)
        return 1
    except urllib.error.URLError as exc:
        print(f"network error: {exc.reason}", file=sys.stderr)
        return 1

    summary = build_summary(items)
    post_slack(summary)
    return 0


if __name__ == "__main__":
    sys.exit(main())
