"""Performance budgets for the hot-path SDK ops.

Numeric budgets with rationale in the docstring. A regression that
introduces an O(n²) lookup or a copy-on-the-hot-path trips a budget
here. Nightly-only via the `perf` marker — hardware variance would
make PR-time gating flaky.

Measurement methodology:

  * Warmup: 50 iterations to populate caches, JIT, etc.
  * Measurement: 500 iterations, wall-clock via `time.perf_counter_ns`.
  * Report p50 / p99 of per-call latency; assert p99 against budget.

Budgets are set HIGH ENOUGH to absorb normal hardware variance but
LOW ENOUGH that an O(n²) or accidental syscall in the hot path
trips them. They're documented sources of truth — DO NOT loosen
them without filing a paper in FEEDBACK.md.
"""

from __future__ import annotations

import time
from statistics import median
from typing import TYPE_CHECKING

import pytest

if TYPE_CHECKING:
    from collections.abc import Callable

    from mgf.hrb import Client

pytestmark = [pytest.mark.perf, pytest.mark.timeout(120)]


def _benchmark(
    op: Callable[[], object], *, warmup: int = 50, measure: int = 500
) -> tuple[float, float]:
    """Run `op()` warmup+measure times; return (p50_us, p99_us) latency."""
    for _ in range(warmup):
        op()
    times: list[int] = []
    for _ in range(measure):
        t0 = time.perf_counter_ns()
        op()
        times.append(time.perf_counter_ns() - t0)
    times.sort()
    p50_us = median(times) / 1000
    p99_us = times[int(len(times) * 0.99)] / 1000
    return p50_us, p99_us


def test_list_latency_budget(broker: Client) -> None:
    """Budget: `Client.list("hw:/test/**")` p99 < 2000µs.

    Rationale: one wire round-trip (send + recv) over a Unix socket
    + JSON parse of a ~80-byte response. A LAN-local Unix socket
    round-trip is ~50-100µs typically; the 2ms budget gives 20x
    headroom for syscall overhead, parser variance, and CI-host
    contention. A regression that adds a per-call syscall (e.g.
    re-reading config) trips this within one push."""
    p50, p99 = _benchmark(lambda: broker.list("hw:/test/**"))
    print(f"\n  list() p50={p50:.0f}µs, p99={p99:.0f}µs")
    assert p99 < 2000, f"list() p99 {p99:.0f}µs over 2000µs budget"


def test_capabilities_latency_budget(broker: Client) -> None:
    """Budget: `Client.capabilities()` p99 < 3000µs.

    Rationale: wire roundtrip + JSON parse of a richer (~400 bytes
    per backend x 3 backends) response, plus the typed projection
    via `from_wire`. The projection allocates per call; a regression
    that adds quadratic-in-fields work shows up here."""
    p50, p99 = _benchmark(broker.capabilities)
    print(f"\n  capabilities() p50={p50:.0f}µs, p99={p99:.0f}µs")
    assert p99 < 3000, f"capabilities() p99 {p99:.0f}µs over 3000µs budget"


def test_claim_release_round_trip_latency_budget(broker: Client) -> None:
    """Budget: claim+release as a paired op p99 < 4000µs.

    Rationale: two wire roundtrips back to back. A regression that
    adds the lease-table search becoming O(N) (instead of O(1)
    swap-remove on a list) shows up at higher concurrency than 1;
    this single-thread test catches the constant-factor regression."""

    def claim_release() -> None:
        lease = broker.claim("hw:/test/dev0", "exclusive")
        broker.release("hw:/test/dev0", lease.lease_id)

    p50, p99 = _benchmark(claim_release, measure=200)
    print(f"\n  claim+release p50={p50:.0f}µs, p99={p99:.0f}µs")
    assert p99 < 4000, f"claim+release p99 {p99:.0f}µs over 4000µs budget"


def test_inspect_latency_budget(broker: Client) -> None:
    """Budget: `Client.inspect()` p99 < 2500µs.

    Rationale: wire roundtrip + JSON parse + typed projection via
    `ResourceDetail.from_wire`. Similar shape to capabilities but
    cheaper (one resource, not a list). The 2.5ms budget gives
    headroom for the projection's enum coercion."""
    p50, p99 = _benchmark(lambda: broker.inspect("hw:/test/dev0"))
    print(f"\n  inspect() p50={p50:.0f}µs, p99={p99:.0f}µs")
    assert p99 < 2500, f"inspect() p99 {p99:.0f}µs over 2500µs budget"


def test_doctor_full_sweep_under_100ms(broker: Client) -> None:
    """Budget: `Client.doctor()` total < 100ms.

    Rationale: 7 checks, each one O(syscall) at worst (stat a
    directory, attempt a connect). The capabilities check is the
    only one that crosses the wire. Total must stay snappy because
    the GUI's doctor pane runs this on every refresh. The 100ms
    budget is 5x the typical observed runtime on a quiet host."""
    t0 = time.perf_counter_ns()
    findings = broker.doctor()
    elapsed_ms = (time.perf_counter_ns() - t0) / 1e6
    print(f"\n  doctor() full sweep: {elapsed_ms:.1f}ms ({len(findings)} findings)")
    assert elapsed_ms < 100, f"doctor() took {elapsed_ms:.1f}ms — over 100ms budget"
