"""Unit tests for AgentrouterError reason decoding.

Builds Connect-JSON error envelopes the way valet emits them (a
``google.rpc.ErrorInfo`` detail with a base64 binary ``value`` and an
optional JSON ``debug``) and asserts the reason is surfaced on the error.
"""

from __future__ import annotations

import base64
import json

from agentrouter_sdk.errors import (
    AgentrouterError,
    _read_proto_field1_string,
    _reason_from_envelope,
    from_http_response,
)


def _varint(n: int) -> bytes:
    out = bytearray()
    while True:
        b = n & 0x7F
        n >>= 7
        if n:
            out.append(b | 0x80)
        else:
            out.append(b)
            return bytes(out)


def _field_string(field_no: int, s: str) -> bytes:
    data = s.encode()
    return _varint((field_no << 3) | 2) + _varint(len(data)) + data


def _error_info_bytes(reason: str, domain: str = "tetrate.ai") -> bytes:
    """Encode a google.rpc.ErrorInfo (reason=field1, domain=field2)."""
    return _field_string(1, reason) + _field_string(2, domain)


def _b64(raw: bytes) -> str:
    return base64.b64encode(raw).decode()


# -- low-level field reader -------------------------------------------


def test_read_field1_skips_later_fields():
    raw = _error_info_bytes("MODEL_NOT_FOUND")
    assert _read_proto_field1_string(raw) == "MODEL_NOT_FOUND"


def test_read_field1_empty_when_field1_absent():
    # Only field 2 (domain) present.
    raw = _field_string(2, "tetrate.ai")
    assert _read_proto_field1_string(raw) == ""


# -- envelope extraction ----------------------------------------------


def test_reason_prefers_debug_json():
    raw = _error_info_bytes("MODEL_NOT_FOUND")
    env = {
        "code": "not_found",
        "message": "model foo not found",
        "details": [
            {
                "type": "google.rpc.ErrorInfo",
                "value": _b64(raw),
                "debug": {"reason": "MODEL_NOT_FOUND", "domain": "tetrate.ai"},
            }
        ],
    }
    assert _reason_from_envelope(env) == "MODEL_NOT_FOUND"


def test_reason_falls_back_to_binary_value_without_debug():
    env = {
        "code": "permission_denied",
        "message": "denied",
        "details": [
            {
                "type": "google.rpc.ErrorInfo",
                "value": _b64(_error_info_bytes("CROSS_TENANT_DENIED")),
            }
        ],
    }
    assert _reason_from_envelope(env) == "CROSS_TENANT_DENIED"


def test_reason_empty_without_error_info_detail():
    assert _reason_from_envelope({"code": "internal", "message": "x"}) == ""
    assert (
        _reason_from_envelope(
            {"details": [{"type": "google.rpc.BadRequest", "value": "AA=="}]}
        )
        == ""
    )


# -- full from_http_response round-trip --------------------------------


def test_from_http_response_surfaces_reason():
    raw = _error_info_bytes("MODEL_NOT_FOUND")
    body = json.dumps(
        {
            "code": "not_found",
            "message": "model foo not found",
            "details": [
                {
                    "type": "google.rpc.ErrorInfo",
                    "value": _b64(raw),
                    "debug": {"reason": "MODEL_NOT_FOUND"},
                }
            ],
        }
    ).encode()
    err = from_http_response(404, body)
    assert isinstance(err, AgentrouterError)
    assert err.code == "not_found"
    assert err.reason == "MODEL_NOT_FOUND"
    assert err.message == "model foo not found"
    assert err.status == 404


def test_from_http_response_empty_reason_when_no_detail():
    body = json.dumps({"code": "unauthenticated", "message": "no"}).encode()
    err = from_http_response(401, body)
    assert err.reason == ""
    assert err.code == "unauthenticated"


def test_constructor_default_reason_is_empty():
    assert AgentrouterError("internal", "boom").reason == ""
