#!/usr/bin/env python3
"""聚合 access.jsonl 过去 24h 的 p50/p95/p99/error_rate → shields.io JSON 徽章。

用法:
    python3 scripts/aggregate_metrics.py [access_log_path] [output_dir]

默认路径:
    access_log_path = /opt/xiangqin/logs/access.jsonl
    output_dir      = docs/assets/metrics/

输出文件:
    <output_dir>/api-p95.json       — shields.io endpoint badge
    <output_dir>/error-rate.json    — 同上
    <output_dir>/latest.json        — 详细 metrics（p50/p95/p99/total/errors）
"""

from __future__ import annotations

import json
import sys
import time
from pathlib import Path

LOG_PATH = Path(sys.argv[1] if len(sys.argv) > 1 else "/opt/xiangqin/logs/access.jsonl")
OUT_DIR = Path(sys.argv[2] if len(sys.argv) > 2 else "docs/assets/metrics")
WINDOW_HOURS = 24


def percentile(values: list[int], p: float) -> int:
    if not values:
        return 0
    ordered = sorted(values)
    idx = int(len(ordered) * p / 100)
    return ordered[min(idx, len(ordered) - 1)]


def shield(label: str, message: str, color: str) -> dict:
    return {
        "schemaVersion": 1,
        "label": label,
        "message": message,
        "color": color,
    }


def main() -> int:
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    cutoff = time.time() - WINDOW_HOURS * 3600

    if not LOG_PATH.exists():
        (OUT_DIR / "api-p95.json").write_text(json.dumps(shield("api p95", "no data", "lightgrey")))
        (OUT_DIR / "error-rate.json").write_text(
            json.dumps(shield("error rate", "no data", "lightgrey"))
        )
        (OUT_DIR / "latest.json").write_text(
            json.dumps({"status": "no_log", "path": str(LOG_PATH)})
        )
        print(f"WARN: log not found at {LOG_PATH}", file=sys.stderr)
        return 0

    latencies: list[int] = []
    errors = 0
    total = 0
    with LOG_PATH.open() as f:
        for line in f:
            try:
                row = json.loads(line)
            except Exception:
                continue
            if row.get("ts", 0) < cutoff:
                continue
            # 不统计 /health（会被 chaos-* workflow / 监控脚本高频打，稀释真实指标）
            if row.get("path", "") == "/health":
                continue
            total += 1
            latencies.append(int(row.get("latency_ms", 0)))
            if int(row.get("status", 0)) >= 500:
                errors += 1

    if total == 0:
        (OUT_DIR / "api-p95.json").write_text(
            json.dumps(shield("api p95", "no traffic", "lightgrey"))
        )
        (OUT_DIR / "error-rate.json").write_text(
            json.dumps(shield("error rate", "no traffic", "lightgrey"))
        )
        (OUT_DIR / "latest.json").write_text(
            json.dumps(
                {
                    "window_hours": WINDOW_HOURS,
                    "total_requests": 0,
                    "generated_at": int(time.time()),
                },
                indent=2,
            )
        )
        print("no non-health requests in window")
        return 0

    p50 = percentile(latencies, 50)
    p95 = percentile(latencies, 95)
    p99 = percentile(latencies, 99)
    err_rate = errors / total * 100

    # p95 → color
    p95_color = (
        "brightgreen"
        if p95 < 100
        else ("green" if p95 < 300 else ("yellow" if p95 < 1000 else "red"))
    )

    # error rate → color
    err_color = "brightgreen" if err_rate < 0.1 else ("yellow" if err_rate < 1 else "red")

    (OUT_DIR / "api-p95.json").write_text(json.dumps(shield("api p95", f"{p95}ms", p95_color)))
    (OUT_DIR / "error-rate.json").write_text(
        json.dumps(shield("error rate", f"{err_rate:.2f}%", err_color))
    )
    (OUT_DIR / "latest.json").write_text(
        json.dumps(
            {
                "window_hours": WINDOW_HOURS,
                "total_requests": total,
                "p50_ms": p50,
                "p95_ms": p95,
                "p99_ms": p99,
                "errors_5xx": errors,
                "error_rate_pct": round(err_rate, 3),
                "generated_at": int(time.time()),
            },
            indent=2,
        )
    )

    print(f"✓ total={total} p50={p50}ms p95={p95}ms p99={p99}ms error_rate={err_rate:.2f}%")
    return 0


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