#!/usr/bin/env python3
"""Print a compact timing summary from a Bazel build event JSON file."""

from __future__ import annotations

import json
import sys
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any


def duration_to_ms(value: Any) -> float | None:
    if value is None:
        return None
    if isinstance(value, (int, float)):
        return float(value)
    if isinstance(value, str):
        if value.endswith("s"):
            try:
                return float(value[:-1]) * 1000
            except ValueError:
                return None
        try:
            return float(value)
        except ValueError:
            return None
    if isinstance(value, dict):
        seconds = value.get("seconds", 0)
        nanos = value.get("nanos", 0)
        try:
            return float(seconds) * 1000 + float(nanos) / 1_000_000
        except (TypeError, ValueError):
            return None
    return None


def format_ms(milliseconds: float | None) -> str | None:
    if milliseconds is None:
        return None
    if milliseconds >= 1000:
        return f"{milliseconds / 1000:.2f}s"
    return f"{milliseconds:.0f}ms"


def nested_get(data: dict[str, Any], *keys: str) -> Any:
    current: Any = data
    for key in keys:
        if not isinstance(current, dict):
            return None
        current = current.get(key)
    return current


def read_events(path: Path) -> list[dict[str, Any]]:
    events = []
    with path.open() as file:
        for line_number, line in enumerate(file, start=1):
            stripped = line.strip()
            if not stripped:
                continue
            try:
                event = json.loads(stripped)
            except json.JSONDecodeError as error:
                print(f"Ignoring malformed BEP line {line_number}: {error}")
                continue
            if isinstance(event, dict):
                events.append(event)
    return events


def collect_metric_events(events: list[dict[str, Any]]) -> list[dict[str, Any]]:
    metric_events = []
    for event in events:
        if isinstance(event.get("buildMetrics"), dict):
            metric_events.append(event["buildMetrics"])
    return metric_events


def first_present(data: dict[str, Any], keys: list[str]) -> Any:
    for key in keys:
        value = data.get(key)
        if value is not None:
            return value
    return None


def summarize_timing(build_metrics: dict[str, Any]) -> list[str]:
    timing = build_metrics.get("timingMetrics")
    if not isinstance(timing, dict):
        return []

    labels = [
        ("Wall time", ["wallTimeInMs", "wallTime", "elapsedTime"]),
        ("Loading phase", ["loadingPhaseTimeInMs", "loadingPhaseTime"]),
        ("Analysis phase", ["analysisPhaseTimeInMs", "analysisPhaseTime"]),
        ("Execution phase", ["executionPhaseTimeInMs", "executionPhaseTime"]),
        ("Critical path", ["criticalPathTimeInMs", "criticalPathTime"]),
    ]

    lines = []
    for label, keys in labels:
        value = format_ms(duration_to_ms(first_present(timing, keys)))
        if value is not None:
            lines.append(f"  {label}: {value}")
    return lines


def summarize_counts(build_metrics: dict[str, Any]) -> list[str]:
    summaries: list[tuple[str, Any]] = []

    action_summary = build_metrics.get("actionSummary")
    if isinstance(action_summary, dict):
        summaries.extend(
            [
                ("Actions created", action_summary.get("actionsCreated")),
                ("Actions executed", action_summary.get("actionsExecuted")),
                ("Action cache hits", action_summary.get("actionCacheHits")),
                ("Remote cache hits", action_summary.get("remoteCacheHits")),
            ]
        )

    target_metrics = build_metrics.get("targetMetrics")
    if isinstance(target_metrics, dict):
        summaries.extend(
            [
                ("Targets configured", target_metrics.get("targetsConfigured")),
                ("Targets loaded", target_metrics.get("targetsLoaded")),
            ]
        )

    package_metrics = build_metrics.get("packageMetrics")
    if isinstance(package_metrics, dict):
        summaries.extend(
            [
                ("Packages loaded", package_metrics.get("packagesLoaded")),
                ("Packages successfully loaded", package_metrics.get("packagesSuccessfullyLoaded")),
            ]
        )

    return [f"  {label}: {value}" for label, value in summaries if value is not None]


def worker_mnemonic(metric: dict[str, Any]) -> str:
    for path in [
        ("mnemonic",),
        ("workerKey", "mnemonic"),
        ("workerKeyInfo", "mnemonic"),
    ]:
        value = nested_get(metric, *path)
        if value:
            return str(value)
    return "unknown"


def worker_actions(metric: dict[str, Any]) -> int | None:
    for key in ["actionsExecuted", "executedActionCount", "actionCount", "actions"]:
        value = metric.get(key)
        if isinstance(value, int):
            return value
        if isinstance(value, str):
            try:
                return int(value)
            except ValueError:
                pass
    return None


def summarize_workers(build_metrics: dict[str, Any]) -> list[str]:
    worker_metrics = build_metrics.get("workerMetrics")
    if not isinstance(worker_metrics, list) or not worker_metrics:
        return []

    counts = Counter()
    actions_by_mnemonic: defaultdict[str, int] = defaultdict(int)
    saw_action_counts = False

    for metric in worker_metrics:
        if not isinstance(metric, dict):
            continue
        mnemonic = worker_mnemonic(metric)
        counts[mnemonic] += 1
        actions = worker_actions(metric)
        if actions is not None:
            saw_action_counts = True
            actions_by_mnemonic[mnemonic] += actions

    if not counts:
        return []

    lines = ["  Worker metric entries:"]
    for mnemonic, count in counts.most_common():
        suffix = ""
        if saw_action_counts:
            suffix = f", actions={actions_by_mnemonic[mnemonic]}"
        lines.append(f"    {mnemonic}: workers={count}{suffix}")
    return lines


def main() -> int:
    if len(sys.argv) != 2:
        print("Usage: summarize_bazel_bep.py <build_event_json_file>")
        return 0

    path = Path(sys.argv[1])
    if not path.exists():
        print(f"Bazel BEP summary: {path} does not exist")
        return 0

    events = read_events(path)
    metric_events = collect_metric_events(events)
    if not metric_events:
        print("Bazel BEP summary: no buildMetrics event found")
        return 0

    build_metrics = metric_events[-1]
    sections = [
        summarize_timing(build_metrics),
        summarize_counts(build_metrics),
        summarize_workers(build_metrics),
    ]
    lines = [line for section in sections for line in section]

    if not lines:
        print("Bazel BEP summary: buildMetrics event had no recognized metrics")
        return 0

    print("Bazel BEP summary:")
    for line in lines:
        print(line)
    return 0


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