"""#82154: an unverified billing verdict must carry its ambiguity through
every downstream surface — the returned terminal response, the structured
result fields, the credential-pool failure_reason, and the persisted entry —
not just the explanatory guidance text.

Anthropic returns the identical "out of extra usage" HTTP 400 body on a
subscription OAuth token both for genuine overage depletion and for a
server-side content-filter rejection of the request. The classifier marks
that verdict ``billing_unverified``; these tests pin that the marking is not
dropped on the way out.
"""

from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import MagicMock

from agent.conversation_loop import _billing_failure_result
from agent.error_classifier import FailoverReason, classify_api_error


class MockAPIError(Exception):
    def __init__(self, message, status_code=None, body=None):
        super().__init__(message)
        self.status_code = status_code
        self.body = body


_EXTRA_USAGE_BODY = (
    "You're out of extra usage. Add more at claude.ai/settings/usage and keep going."
)


def _classified_unverified():
    e = MockAPIError(
        _EXTRA_USAGE_BODY,
        status_code=400,
        body={"error": {"type": "invalid_request_error", "message": _EXTRA_USAGE_BODY}},
    )
    return classify_api_error(e, provider="anthropic")


def _classified_confirmed():
    e = MockAPIError(
        "Your credit balance is too low to access the Anthropic API.",
        status_code=400,
        body={"error": {
            "type": "invalid_request_error",
            "message": "Your credit balance is too low to access the Anthropic API.",
        }},
    )
    return classify_api_error(e, provider="anthropic")


# ── Returned terminal response ───────────────────────────────────────────────


class TestTerminalResponse:
    def test_unverified_terminal_response_does_not_assert_billing(self):
        """The exact ambiguous 400 must not produce an unhedged
        'Billing or credits exhausted' terminal response."""
        result = _billing_failure_result(
            classified=_classified_unverified(),
            summary="HTTP 400: out of extra usage",
            messages=[],
            api_call_count=3,
            provider="anthropic",
            base_url="https://api.anthropic.com",
            model="claude-opus-5",
        )
        final = result["final_response"]
        assert not final.startswith("Billing or credits exhausted")
        assert "unverified" in final
        assert "content-filter" in final or "content filter" in final

    def test_unverified_terminal_response_structured_fields(self):
        """The structured result carries the ambiguity, not just the prose."""
        result = _billing_failure_result(
            classified=_classified_unverified(),
            summary="HTTP 400: out of extra usage",
            messages=[],
            api_call_count=3,
            provider="anthropic",
            base_url="https://api.anthropic.com",
            model="claude-opus-5",
        )
        assert result["failed"] is True
        assert result["failure_reason"] == "billing"
        assert result["billing_unverified"] is True
        block = result["billing_block"]
        if block is not None:  # None only if billing_links is unavailable
            assert block.get("unverified") is True

    def test_confirmed_terminal_response_stays_assertive(self):
        """A confirmed billing verdict keeps the original terminal label and
        carries no ambiguity flag."""
        result = _billing_failure_result(
            classified=_classified_confirmed(),
            summary="HTTP 400: credit balance too low",
            messages=[],
            api_call_count=1,
            provider="anthropic",
            base_url="https://api.anthropic.com",
            model="claude-opus-5",
        )
        assert result["final_response"].startswith("Billing or credits exhausted")
        assert result["billing_unverified"] is False
        block = result["billing_block"]
        if block is not None:
            assert "unverified" not in block



# ── Credential-pool plumbing ─────────────────────────────────────────────────


class TestPoolFailureReason:
    def _run_recovery(self, *, billing_unverified: bool) -> dict:
        """Drive recover_with_credential_pool with a billing classification and
        capture what the pool is told."""
        from agent.agent_runtime_helpers import recover_with_credential_pool

        captured: dict = {}
        next_entry = SimpleNamespace(label="secondary")

        class _Pool:
            provider = "anthropic"

            def current(self):
                return None

            def entries(self):
                return []

            def mark_exhausted_and_rotate(self, **kwargs):
                captured.update(kwargs)
                return next_entry

        agent = SimpleNamespace(
            provider="anthropic",
            base_url="https://api.anthropic.com",
            api_key="sk-ant-oat01-test",
            _credential_pool=_Pool(),
            _credential_pool_entry_id=None,
            _swap_credential=MagicMock(),
        )
        recovered, _ = recover_with_credential_pool(
            agent,
            status_code=400,
            has_retried_429=False,
            classified_reason=FailoverReason.billing,
            billing_unverified=billing_unverified,
        )
        assert recovered is True
        return captured

    def test_unverified_billing_reaches_pool_as_unverified(self):
        captured = self._run_recovery(billing_unverified=True)
        assert captured["failure_reason"] == "billing_unverified"

    def test_confirmed_billing_reaches_pool_as_billing(self):
        captured = self._run_recovery(billing_unverified=False)
        assert captured["failure_reason"] == "billing"
