"""PC-01: Suite throughput at default ``max_concurrency``.

`Suite.run` orchestrates per-probe execution through an
`asyncio.Semaphore(max_concurrency)` gate. The harness's capacity
shape — how many probes/sec a default-config suite sustains —
depends on:

  - Per-probe wrapper tax (PC-02 pins it ≤ 100 µs).
  - Check-chain time per probe (PC-03 pins it ≤ 250 µs).
  - Concurrency cap (default 4).
  - Network latency (uncontrolled; eliminated here via stub
    transport).

With a stub transport returning a canned Response immediately,
the only cost left is the apiprobe harness itself: wrapper tax +
check chain + asyncio gather/Semaphore orchestration. This is the
**capacity floor** consumers can lean on — a regression that
tanks throughput below this floor is a real harness-side issue.

Floor: ≥ 50 probes/sec for a 100-probe DEFAULT_CHECKS suite at
`max_concurrency=4`. Locally the harness clears tens of thousands
per second; 50 is the regression-detection threshold (a 50x drop
would still pass — the assertion catches order-of-magnitude
collapses, not creep).

Marker: ``slow`` (registered in pyproject.toml). Excluded from
the default suite; run via ``pytest -m slow`` for the nightly
capacity sweep.
"""

from __future__ import annotations

import asyncio
import time
from types import MappingProxyType
from typing import TYPE_CHECKING

import pytest

from mgf.apiprobe.checks import DEFAULT_CHECKS
from mgf.apiprobe.core.probe import Probe
from mgf.apiprobe.core.response import Response
from mgf.apiprobe.core.suite import Suite
from mgf.apiprobe.core.transport import Transport

if TYPE_CHECKING:
    from mgf.apiprobe.core.context import Context


# Capacity floor. The harness should clear thousands of probes/sec
# locally; 50 is the regression-detection level — a serious
# orchestration regression (e.g. accidental sequential execution
# despite the semaphore, a per-probe sleep, a synchronous I/O
# call in the hot path) would slip below this.
_MIN_PROBES_PER_SEC = 50.0


class _StubTransport(Transport):
    """Zero-latency Transport returning a fixed canned Response."""

    def __init__(self) -> None:
        self._response = Response(
            url="https://api.example.com/items",
            status=200,
            headers=MappingProxyType(
                {
                    "content-type": "application/json",
                    "x-request-id": "stub",
                    "x-ratelimit-limit": "1000",
                    "strict-transport-security": "max-age=31536000",
                    "x-content-type-options": "nosniff",
                    "x-frame-options": "DENY",
                    "server": "stub/1.0",
                }
            ),
            body=b'{"ok": true}',
            elapsed_ms=0.1,
            request_id="stub",
        )

    async def send(self, probe: Probe, ctx: Context) -> Response:
        return self._response


@pytest.mark.perf
class TestSuiteThroughput:
    """Pin the harness's capacity floor at default max_concurrency."""

    def test_100_probe_suite_sustains_floor(self) -> None:
        """100 probes with DEFAULT_CHECKS clears the floor."""
        probes = [
            Probe.get("/items").id(f"probe-{i}").base("https://api.example.com").expect_status(200)
            for i in range(100)
        ]
        suite = Suite(probes=probes, checks=DEFAULT_CHECKS)
        transport = _StubTransport()

        async def _drive() -> float:
            # Warm-up — one full run pays first-call costs.
            await suite.run(transport=transport)

            n_runs = 5
            start = time.perf_counter()
            for _ in range(n_runs):
                await suite.run(transport=transport)
            elapsed = time.perf_counter() - start

            total_probes = n_runs * len(suite.probes)
            return total_probes / elapsed

        probes_per_sec = asyncio.run(_drive())

        assert probes_per_sec >= _MIN_PROBES_PER_SEC, (
            f"PC-01: suite throughput floor breach: "
            f"{probes_per_sec:.1f} probes/sec (floor "
            f"{_MIN_PROBES_PER_SEC:.0f} probes/sec). "
            f"Investigate Suite.run orchestration in "
            f"src/mgf/apiprobe/core/suite.py — likely a regression "
            f"in the asyncio.gather + Semaphore loop or a slow "
            f"per-probe path (see PC-02 / PC-03 for the per-probe "
            f"and check-chain budgets)."
        )
