"""Tests for ai4gh.core.transport."""

from __future__ import annotations

import httpx
import pytest
from pytest_httpx import HTTPXMock

from ai4gh.core.config import Config
from ai4gh.core.exceptions import (
    NotFoundError,
    RateLimitError,
    ServerError,
    TransportError,
)
from ai4gh.core.transport import Transport


@pytest.fixture
def transport() -> Transport:
    return Transport(Config(timeout=5.0))


class TestTransportInit:
    def test_default_config(self) -> None:
        t = Transport()
        assert t._config.timeout == 30.0

    def test_custom_config(self) -> None:
        t = Transport(Config(timeout=10.0))
        assert t._config.timeout == 10.0


class TestTransportClient:
    def test_creates_client_on_access(self) -> None:
        t = Transport()
        assert t._client is None
        client = t.client
        assert isinstance(client, httpx.AsyncClient)

    def test_reuses_client(self) -> None:
        t = Transport()
        c1 = t.client
        c2 = t.client
        assert c1 is c2


class TestTransportClose:
    @pytest.mark.asyncio
    async def test_close(self) -> None:
        t = Transport()
        _ = t.client  # force creation
        await t.close()
        assert t._client is None

    @pytest.mark.asyncio
    async def test_close_when_no_client(self) -> None:
        t = Transport()
        await t.close()  # should not raise


class TestRaiseForStatus:
    def test_success_does_nothing(self, transport: Transport) -> None:
        response = httpx.Response(200, text="ok")
        transport._raise_for_status(response)

    def test_404_raises_not_found(self, transport: Transport) -> None:
        response = httpx.Response(404, text="not found")
        with pytest.raises(NotFoundError):
            transport._raise_for_status(response)

    def test_429_raises_rate_limit(self, transport: Transport) -> None:
        response = httpx.Response(429, text="too many requests")
        with pytest.raises(RateLimitError):
            transport._raise_for_status(response)

    def test_429_with_retry_after(self, transport: Transport) -> None:
        response = httpx.Response(429, text="slow down", headers={"Retry-After": "30"})
        with pytest.raises(RateLimitError) as exc_info:
            transport._raise_for_status(response)
        assert exc_info.value.retry_after == 30.0

    def test_500_raises_server_error(self, transport: Transport) -> None:
        response = httpx.Response(500, text="internal error")
        with pytest.raises(ServerError):
            transport._raise_for_status(response)

    def test_503_raises_server_error(self, transport: Transport) -> None:
        response = httpx.Response(503, text="unavailable")
        with pytest.raises(ServerError):
            transport._raise_for_status(response)

    def test_400_raises_transport_error(self, transport: Transport) -> None:
        response = httpx.Response(400, text="bad request")
        with pytest.raises(TransportError):
            transport._raise_for_status(response)

    def test_json_error_message(self, transport: Transport) -> None:
        response = httpx.Response(
            400,
            json={"message": "Invalid parameter"},
        )
        with pytest.raises(TransportError, match="Invalid parameter"):
            transport._raise_for_status(response)


class TestTransportGet:
    @pytest.mark.asyncio
    async def test_get_success(self, transport: Transport, httpx_mock: HTTPXMock) -> None:
        httpx_mock.add_response(url="https://example.org/test", json={"ok": True})
        response = await transport.get("https://example.org/test")
        assert response.json() == {"ok": True}

    @pytest.mark.asyncio
    async def test_get_with_params(self, transport: Transport, httpx_mock: HTTPXMock) -> None:
        httpx_mock.add_response(json={"result": "filtered"})
        response = await transport.get("https://example.org/search", params={"q": "test"})
        assert response.json() == {"result": "filtered"}

    @pytest.mark.asyncio
    async def test_get_not_found(self, transport: Transport, httpx_mock: HTTPXMock) -> None:
        httpx_mock.add_response(status_code=404, text="not found")
        with pytest.raises(NotFoundError):
            await transport.get("https://example.org/missing")


class TestTransportPost:
    @pytest.mark.asyncio
    async def test_post_success(self, transport: Transport, httpx_mock: HTTPXMock) -> None:
        httpx_mock.add_response(url="https://example.org/data", json={"id": "123"}, status_code=201)
        response = await transport.post("https://example.org/data", json={"name": "test"})
        assert response.json() == {"id": "123"}


class TestTransportDelete:
    @pytest.mark.asyncio
    async def test_delete_success(self, transport: Transport, httpx_mock: HTTPXMock) -> None:
        httpx_mock.add_response(url="https://example.org/items/1", status_code=204, text="")
        response = await transport.delete("https://example.org/items/1")
        assert response.status_code == 204


class TestTransportErrorHandling:
    @pytest.mark.asyncio
    async def test_timeout_raises_transport_error(
        self, transport: Transport, httpx_mock: HTTPXMock
    ) -> None:
        httpx_mock.add_exception(httpx.ReadTimeout("timed out"))
        with pytest.raises(TransportError, match="Request timed out"):
            await transport._request("GET", "https://example.org/slow")

    @pytest.mark.asyncio
    async def test_connect_error_raises_transport_error(
        self, transport: Transport, httpx_mock: HTTPXMock
    ) -> None:
        httpx_mock.add_exception(httpx.ConnectError("connection refused"))
        with pytest.raises(TransportError, match="Connection failed"):
            await transport._request("GET", "https://example.org/down")
