import pytest
import httpx

from a3api import (
    A3Client,
    AssessAgeRequest,
    AssessAgeResponse,
    OsSignal,
    Verdict,
    AgeBracket,
)
from a3api._errors import (
    A3ApiError,
    A3AuthenticationError,
    A3ConnectionError,
    A3RateLimitError,
    A3ValidationError,
)
from tests.conftest import MOCK_RESPONSE_JSON


class TestA3ClientInit:
    def test_empty_api_key_raises(self):
        with pytest.raises(ValueError, match="api_key is required"):
            A3Client("")

    def test_trailing_slash_stripped(self):
        client = A3Client("sk_test", base_url="https://example.com/")
        assert client._base_url == "https://example.com"
        client.close()


class TestAssessAge:
    def test_success(self, sync_client: A3Client, mock_success, httpx_mock):
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        resp = sync_client.assess_age(req)

        assert isinstance(resp, AssessAgeResponse)
        assert resp.verdict == Verdict.CONSISTENT
        assert resp.assessed_age_bracket == AgeBracket.AGE_18_PLUS
        assert resp.verification_token == "eyJ0ZXN0IjoiMSJ9.abc123"

    def test_sends_correct_headers(self, sync_client: A3Client, mock_success, httpx_mock):
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        sync_client.assess_age(req)

        sent = httpx_mock.get_request()
        assert sent.headers["x-api-key"] == "sk_test"
        assert sent.headers["content-type"] == "application/json"
        assert "a3api-python/" in sent.headers["user-agent"]

    def test_400_raises_validation_error(self, sync_client: A3Client, httpx_mock):
        httpx_mock.add_response(
            url="https://test.a3api.io/v1/assurance/assess-age",
            method="POST",
            status_code=400,
            json={
                "statusCode": 400,
                "message": ["os_signal must be a valid enum value"],
                "error": "Bad Request",
            },
        )
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        with pytest.raises(A3ValidationError) as exc_info:
            sync_client.assess_age(req)
        assert exc_info.value.validation_errors == ["os_signal must be a valid enum value"]

    def test_401_raises_auth_error(self, sync_client: A3Client, httpx_mock):
        httpx_mock.add_response(
            url="https://test.a3api.io/v1/assurance/assess-age",
            method="POST",
            status_code=401,
            json={
                "statusCode": 401,
                "message": "Unauthorized",
                "error": "Unauthorized",
            },
        )
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        with pytest.raises(A3AuthenticationError):
            sync_client.assess_age(req)

    def test_429_raises_rate_limit_error(self, sync_client: A3Client, httpx_mock):
        httpx_mock.add_response(
            url="https://test.a3api.io/v1/assurance/assess-age",
            method="POST",
            status_code=429,
            headers={"retry-after": "10"},
            json={
                "statusCode": 429,
                "message": "Too Many Requests",
                "error": "Too Many Requests",
            },
        )
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        with pytest.raises(A3RateLimitError) as exc_info:
            sync_client.assess_age(req)
        assert exc_info.value.retry_after == 10.0

    def test_500_raises_api_error(self, sync_client: A3Client, httpx_mock):
        httpx_mock.add_response(
            url="https://test.a3api.io/v1/assurance/assess-age",
            method="POST",
            status_code=500,
            json={
                "statusCode": 500,
                "message": "Internal Server Error",
                "error": "Internal Server Error",
            },
        )
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        with pytest.raises(A3ApiError) as exc_info:
            sync_client.assess_age(req)
        assert exc_info.value.status_code == 500

    def test_assess_age_raw_returns_dict(self, sync_client: A3Client, mock_success, httpx_mock):
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        raw = sync_client.assess_age_raw(req)
        assert isinstance(raw, dict)
        assert raw["verdict"] == "CONSISTENT"

    def test_context_manager(self, mock_success, httpx_mock):
        with A3Client("sk_test", base_url="https://test.a3api.io", max_retries=0) as client:
            req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
            resp = client.assess_age(req)
            assert resp.verdict == Verdict.CONSISTENT

    def test_non_json_error_body(self, sync_client: A3Client, httpx_mock):
        httpx_mock.add_response(
            url="https://test.a3api.io/v1/assurance/assess-age",
            method="POST",
            status_code=502,
            text="<html>Bad Gateway</html>",
        )
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        with pytest.raises(A3ApiError) as exc_info:
            sync_client.assess_age(req)
        assert exc_info.value.status_code == 502
        assert exc_info.value.body is None

    def test_timeout_raises_connection_error(self, httpx_mock):
        httpx_mock.add_exception(
            httpx.ReadTimeout("timed out"),
            url="https://test.a3api.io/v1/assurance/assess-age",
        )
        client = A3Client("sk_test", base_url="https://test.a3api.io", max_retries=0)
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        with pytest.raises(A3ConnectionError, match="timed out"):
            client.assess_age(req)
        client.close()

    def test_connect_error_raises_connection_error(self, httpx_mock):
        httpx_mock.add_exception(
            httpx.ConnectError("connection refused"),
            url="https://test.a3api.io/v1/assurance/assess-age",
        )
        client = A3Client("sk_test", base_url="https://test.a3api.io", max_retries=0)
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        with pytest.raises(A3ConnectionError, match="Network error"):
            client.assess_age(req)
        client.close()

    def test_read_error_raises_connection_error(self, httpx_mock):
        httpx_mock.add_exception(
            httpx.ReadError("connection reset"),
            url="https://test.a3api.io/v1/assurance/assess-age",
        )
        client = A3Client("sk_test", base_url="https://test.a3api.io", max_retries=0)
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        with pytest.raises(A3ConnectionError, match="Network error"):
            client.assess_age(req)
        client.close()


class TestRetryBehavior:
    def test_retries_429_then_succeeds(self, httpx_mock):
        httpx_mock.add_response(
            url="https://test.a3api.io/v1/assurance/assess-age",
            method="POST",
            status_code=429,
            headers={"retry-after": "0"},
            json={"statusCode": 429, "message": "Rate limited", "error": "Too Many Requests"},
        )
        httpx_mock.add_response(
            url="https://test.a3api.io/v1/assurance/assess-age",
            method="POST",
            json=MOCK_RESPONSE_JSON,
        )
        client = A3Client("sk_test", base_url="https://test.a3api.io", max_retries=2)
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        resp = client.assess_age(req)
        assert resp.verdict == Verdict.CONSISTENT
        client.close()

    def test_does_not_retry_400(self, httpx_mock):
        httpx_mock.add_response(
            url="https://test.a3api.io/v1/assurance/assess-age",
            method="POST",
            status_code=400,
            json={"statusCode": 400, "message": ["invalid"], "error": "Bad Request"},
        )
        client = A3Client("sk_test", base_url="https://test.a3api.io", max_retries=2)
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        with pytest.raises(A3ValidationError):
            client.assess_age(req)
        # Only 1 request should have been made
        assert len(httpx_mock.get_requests()) == 1
        client.close()

    def test_does_not_retry_401(self, httpx_mock):
        httpx_mock.add_response(
            url="https://test.a3api.io/v1/assurance/assess-age",
            method="POST",
            status_code=401,
            json={"statusCode": 401, "message": "Unauthorized", "error": "Unauthorized"},
        )
        client = A3Client("sk_test", base_url="https://test.a3api.io", max_retries=2)
        req = AssessAgeRequest(os_signal=OsSignal.AGE_18_PLUS, user_country_code="US")
        with pytest.raises(A3AuthenticationError):
            client.assess_age(req)
        assert len(httpx_mock.get_requests()) == 1
        client.close()
