from __future__ import annotations

import json

import httpx
import pytest

from contract_attestation.client import AsyncClient, SyncClient
from contract_attestation.errors import (
    AuthenticationError,
    BadRequestError,
    ConnectionError,
    PaymentRequiredError,
    RateLimitError,
    ServiceUnavailableError,
    TimeoutError,
)


def _review_payload() -> dict:
    return {
        "score": 90,
        "evidence_level": "full",
        "findings": [],
        "recommendations": [],
        "attestation": {},
        "attestation_status": "attested",
    }


@pytest.mark.anyio
async def test_async_review_success_and_remaining_header() -> None:
    def handler(request: httpx.Request) -> httpx.Response:
        assert request.method == "POST"
        assert request.url.path == "/review/"
        payload = json.loads(request.content.decode())
        assert payload["reviewType"] == "security"
        assert payload["encryptionKey"] == "0xabc"
        return httpx.Response(
            200,
            json=_review_payload(),
            headers={"X-API-Key-Remaining": "24"},
        )

    transport = httpx.MockTransport(handler)
    raw = httpx.AsyncClient(base_url="https://example.test", transport=transport)
    client = AsyncClient(_client=raw)

    result = await client.review("contract C {}", encryption_key="0xabc")

    assert result.score == 90
    assert result.remaining_reviews == 24
    await client.close()


@pytest.mark.anyio
async def test_async_review_uses_free_remaining_header() -> None:
    def handler(_request: httpx.Request) -> httpx.Response:
        return httpx.Response(
            200,
            json=_review_payload(),
            headers={"X-Free-Tier-Remaining": "2"},
        )

    transport = httpx.MockTransport(handler)
    raw = httpx.AsyncClient(base_url="https://example.test", transport=transport)
    client = AsyncClient(_client=raw)

    result = await client.review("contract C {}")

    assert result.remaining_reviews == 2
    await client.close()


@pytest.mark.anyio
@pytest.mark.parametrize(
    ("status_code", "error_cls"),
    [
        (400, BadRequestError),
        (401, AuthenticationError),
        (402, PaymentRequiredError),
        (503, ServiceUnavailableError),
        (504, TimeoutError),
    ],
)
async def test_async_client_error_mapping(status_code: int, error_cls: type[Exception]) -> None:
    def handler(_request: httpx.Request) -> httpx.Response:
        return httpx.Response(status_code, json={"detail": "boom"})

    transport = httpx.MockTransport(handler)
    raw = httpx.AsyncClient(base_url="https://example.test", transport=transport)
    client = AsyncClient(_client=raw)

    with pytest.raises(error_cls):
        await client.health()

    await client.close()


@pytest.mark.anyio
async def test_async_client_rate_limit_error_parses_retry_after() -> None:
    def handler(_request: httpx.Request) -> httpx.Response:
        return httpx.Response(
            429,
            json={"error": "Rate limited", "detail": "try later"},
            headers={"Retry-After": "15"},
        )

    transport = httpx.MockTransport(handler)
    raw = httpx.AsyncClient(base_url="https://example.test", transport=transport)
    client = AsyncClient(_client=raw)

    with pytest.raises(RateLimitError) as exc_info:
        await client.health()

    assert exc_info.value.retry_after == 15
    await client.close()


@pytest.mark.anyio
async def test_async_client_timeout_and_connection_errors() -> None:
    def timeout_handler(request: httpx.Request) -> httpx.Response:
        raise httpx.ReadTimeout("timed out", request=request)

    timeout_transport = httpx.MockTransport(timeout_handler)
    timeout_raw = httpx.AsyncClient(base_url="https://example.test", transport=timeout_transport)
    timeout_client = AsyncClient(_client=timeout_raw)

    with pytest.raises(TimeoutError):
        await timeout_client.health()

    await timeout_client.close()

    def connection_handler(request: httpx.Request) -> httpx.Response:
        raise httpx.ConnectError("no route", request=request)

    connection_transport = httpx.MockTransport(connection_handler)
    connection_raw = httpx.AsyncClient(base_url="https://example.test", transport=connection_transport)
    connection_client = AsyncClient(_client=connection_raw)

    with pytest.raises(ConnectionError):
        await connection_client.health()

    await connection_client.close()


def test_sync_client_success_and_status_lookup() -> None:
    def handler(request: httpx.Request) -> httpx.Response:
        if request.url.path == "/review/":
            return httpx.Response(
                200,
                json=_review_payload(),
                headers={"X-Free-Tier-Remaining": "1"},
            )
        if request.url.path == "/status/0x" + ("11" * 32):
            return httpx.Response(
                200,
                json={
                    "request_hash": "0x" + ("11" * 32),
                    "status": "attested",
                    "validator_address": "0x123",
                    "agent_id": 297,
                    "score": 90,
                    "response_hash": "0x" + ("ab" * 32),
                    "tag": "validator-analyzed",
                    "last_update": 1700000000,
                    "response_uri": None,
                },
            )
        raise AssertionError(f"unexpected path: {request.url.path}")

    transport = httpx.MockTransport(handler)
    raw = httpx.Client(base_url="https://example.test", transport=transport)
    client = SyncClient(_client=raw)

    review = client.review("contract C {}")
    status = client.status("0x" + ("11" * 32))

    assert review.remaining_reviews == 1
    assert status.status.value == "attested"
    client.close()


def test_sync_client_maps_payment_required() -> None:
    def handler(_request: httpx.Request) -> httpx.Response:
        return httpx.Response(402, json={"error": "Quota exhausted"})

    transport = httpx.MockTransport(handler)
    raw = httpx.Client(base_url="https://example.test", transport=transport)
    client = SyncClient(_client=raw)

    with pytest.raises(PaymentRequiredError):
        client.review("contract C {}")

    client.close()
