"""Testes do transporte HTTP — envelope, retries e mapeamento de erros."""

from __future__ import annotations

import httpx
import pytest
import respx

from abacapython import AbacaPay
from abacapython.exceptions import (
    APIConnectionError,
    APIResponseError,
    APITimeoutError,
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    ServerError,
)

BASE = "https://api.test.abacatepay.local/v2"


@pytest.fixture
def client():
    cli = AbacaPay(api_key="abc_test", base_url=BASE, max_retries=0)
    yield cli
    cli.close()


@respx.mock
def test_unwraps_envelope_data(client):
    route = respx.get(f"{BASE}/store/get").respond(
        200, json={"data": {"id": "s1", "name": "Loja"}, "error": None, "success": True}
    )
    store = client.store.get()
    assert route.called
    assert store.id == "s1"
    assert store.name == "Loja"


@respx.mock
def test_unwraps_returns_dict_when_no_envelope(client):
    respx.get(f"{BASE}/store/get").respond(200, json={"id": "s2", "name": "Outra"})
    store = client.store.get()
    assert store.id == "s2"


@respx.mock
def test_raises_authentication_error(client):
    respx.get(f"{BASE}/store/get").respond(401, json={"error": {"message": "API key inválida"}})
    with pytest.raises(AuthenticationError) as exc:
        client.store.get()
    assert exc.value.status_code == 401
    assert "API key" in exc.value.message


@respx.mock
def test_raises_not_found(client):
    respx.get(f"{BASE}/customers/get").respond(404, json={"error": {"message": "Não encontrado"}})
    with pytest.raises(NotFoundError):
        client.customers.get(id="cust_x")


@respx.mock
def test_raises_rate_limit(client):
    respx.get(f"{BASE}/store/get").respond(429, json={"error": "muitas requisições"})
    with pytest.raises(RateLimitError):
        client.store.get()


@respx.mock
def test_raises_server_error(client):
    respx.get(f"{BASE}/store/get").respond(503, text="indisponivel")
    with pytest.raises(ServerError):
        client.store.get()


@respx.mock
def test_envelope_with_error_field():
    cli = AbacaPay(api_key="abc", base_url=BASE, max_retries=0)
    respx.get(f"{BASE}/store/get").respond(
        200, json={"data": None, "error": {"message": "limite excedido"}, "success": False}
    )
    with pytest.raises(APIResponseError, match="limite excedido"):
        cli.store.get()
    cli.close()


@respx.mock
def test_retries_on_5xx_then_succeeds():
    cli = AbacaPay(api_key="abc", base_url=BASE, max_retries=2, backoff_factor=0.0)
    route = respx.get(f"{BASE}/store/get").mock(
        side_effect=[
            httpx.Response(503, json={"error": "indisponível"}),
            httpx.Response(200, json={"data": {"id": "s9"}, "error": None}),
        ]
    )
    store = cli.store.get()
    assert store.id == "s9"
    assert route.call_count == 2
    cli.close()


@respx.mock
def test_retry_exhausted_raises():
    cli = AbacaPay(api_key="abc", base_url=BASE, max_retries=1, backoff_factor=0.0)
    respx.get(f"{BASE}/store/get").mock(
        return_value=httpx.Response(502, json={"error": "bad gateway"})
    )
    with pytest.raises(ServerError):
        cli.store.get()
    cli.close()


@respx.mock
def test_timeout_maps_to_timeout_error():
    cli = AbacaPay(api_key="abc", base_url=BASE, max_retries=0)
    respx.get(f"{BASE}/store/get").mock(side_effect=httpx.TimeoutException("slow"))
    with pytest.raises(APITimeoutError):
        cli.store.get()
    cli.close()


@respx.mock
def test_connection_error_mapped():
    cli = AbacaPay(api_key="abc", base_url=BASE, max_retries=0)
    respx.get(f"{BASE}/store/get").mock(side_effect=httpx.ConnectError("dns"))
    with pytest.raises(APIConnectionError):
        cli.store.get()
    cli.close()


@respx.mock
def test_authorization_header_sent(client):
    route = respx.get(f"{BASE}/store/get").respond(200, json={"data": {"id": "s"}})
    client.store.get()
    request = route.calls[0].request
    assert request.headers["authorization"] == "Bearer abc_test"
    assert request.headers["user-agent"].startswith("abacapython/")
