"""Quality checks — non-functional response characteristics.

See: ``DESIGN.md`` §7.4.

3 checks:

  - ``LatencyOverBudget``      — ``elapsed_ms > probe.expected_latency_ms``
  - ``LatencySuspiciousZero`` — zero / negative latency means the harness is broken
  - ``BodySizeOverBudget``    — body exceeds the threshold (default 5 MB)
"""

from __future__ import annotations

from typing import TYPE_CHECKING, ClassVar

from mgf.apiprobe.core.check import Check
from mgf.apiprobe.core.finding import RootCause, Severity

if TYPE_CHECKING:
    from collections.abc import Iterable

    from mgf.apiprobe.core.context import Context
    from mgf.apiprobe.core.finding import Finding
    from mgf.apiprobe.core.probe import Probe
    from mgf.apiprobe.core.response import Response


# Default body-size cap for v0.1. Streaming responses (which CAN be
# arbitrarily large) are deferred to v0.2.
DEFAULT_BODY_SIZE_BUDGET_BYTES: int = 5 * 1024 * 1024


class LatencyOverBudget(Check):
    """Response latency exceeded ``probe.expected_latency_ms``."""

    code: ClassVar[str] = "quality.latency.over-budget"
    severity_default: ClassVar[Severity] = Severity.WARN
    root_cause_default: ClassVar[RootCause] = RootCause.PROVIDER_QUALITY

    def run(
        self,
        probe: Probe,
        response: Response,
        ctx: Context,
    ) -> Iterable[Finding]:
        if probe.expected_latency_ms is None:
            return
        if response.elapsed_ms <= probe.expected_latency_ms:
            return
        yield self.finding(
            probe_id=probe.probe_id,
            message=(
                f"Response latency {response.elapsed_ms:.1f} ms exceeded "
                f"budget {probe.expected_latency_ms:.1f} ms."
            ),
            evidence={
                "elapsed_ms": response.elapsed_ms,
                "budget_ms": probe.expected_latency_ms,
                "over_by_ms": response.elapsed_ms - probe.expected_latency_ms,
            },
            remediation=(
                "Investigate provider-side latency: deploys, DB load, "
                "downstream dependencies. If the budget is wrong, raise "
                "expect_latency_ms(...) on the probe."
            ),
        )


class LatencySuspiciousZero(Check):
    """Zero or negative ``elapsed_ms`` indicates the harness is broken.

    Promotes the root cause to ``INFRASTRUCTURE`` per DESIGN.md §3.2.1
    (the harness measured wrong, not the provider's quality).
    """

    code: ClassVar[str] = "quality.latency.suspicious-zero"
    severity_default: ClassVar[Severity] = Severity.CRITICAL
    root_cause_default: ClassVar[RootCause] = RootCause.INFRASTRUCTURE

    def run(
        self,
        probe: Probe,
        response: Response,
        ctx: Context,
    ) -> Iterable[Finding]:
        if response.elapsed_ms > 0:
            return
        yield self.finding(
            probe_id=probe.probe_id,
            message=(
                f"Suspicious latency {response.elapsed_ms!r} — measurement "
                f"is broken; probe results below this finding may be "
                f"unreliable."
            ),
            evidence={"elapsed_ms": response.elapsed_ms, "url": response.url},
            remediation=(
                "Check the transport implementation: clock skew, "
                "monotonic-clock issues, or a mocked Response with an "
                "unset elapsed_ms field."
            ),
        )


class BodySizeOverBudget(Check):
    """Response body exceeds the size threshold.

    Threshold is configurable via the constructor; default
    :data:`DEFAULT_BODY_SIZE_BUDGET_BYTES` (5 MB). Tightens
    progressively in higher phases when streaming bodies land.
    """

    code: ClassVar[str] = "quality.body-size.over-budget"
    severity_default: ClassVar[Severity] = Severity.WARN
    root_cause_default: ClassVar[RootCause] = RootCause.PROVIDER_QUALITY

    def __init__(self, threshold_bytes: int = DEFAULT_BODY_SIZE_BUDGET_BYTES) -> None:
        if threshold_bytes <= 0:
            raise ValueError(f"threshold_bytes must be positive, got {threshold_bytes!r}")
        self.threshold_bytes = threshold_bytes

    def run(
        self,
        probe: Probe,
        response: Response,
        ctx: Context,
    ) -> Iterable[Finding]:
        size = len(response.body)
        if size <= self.threshold_bytes:
            return
        yield self.finding(
            probe_id=probe.probe_id,
            message=(f"Response body size {size} B exceeded threshold {self.threshold_bytes} B."),
            evidence={
                "body_bytes": size,
                "threshold_bytes": self.threshold_bytes,
                "url": response.url,
            },
            remediation=(
                "Provider returned a body larger than the configured "
                "threshold. Consider pagination, streaming, or a larger "
                "BodySizeOverBudget threshold."
            ),
        )


__all__ = [
    "DEFAULT_BODY_SIZE_BUDGET_BYTES",
    "BodySizeOverBudget",
    "LatencyOverBudget",
    "LatencySuspiciousZero",
]
