"""Tests for the MLD HTTP client."""

import json

import httpx
import pytest

from mld_sdk.client._exceptions import (
    AuthenticationError,
    MLDConnectionError,
    NotFoundError,
    ServerError,
)
from mld_sdk.client._http import HTTPClient


def _mock_transport(
    status: int = 200,
    body: dict | None = None,
    handler: object = None,
) -> httpx.MockTransport:
    """Create a mock transport returning a fixed response."""

    def _handler(request: httpx.Request) -> httpx.Response:
        if callable(handler):
            return handler(request)
        return httpx.Response(
            status_code=status,
            json=body if body is not None else {},
        )

    return httpx.MockTransport(_handler)


class TestHTTPClientBasics:
    def test_get_prepends_api_prefix(self):
        requests_made: list[httpx.Request] = []

        def handler(request: httpx.Request) -> httpx.Response:
            requests_made.append(request)
            return httpx.Response(200, json={"ok": True})

        client = HTTPClient("http://localhost:8001")
        client._client = httpx.Client(
            base_url="http://localhost:8001",
            transport=_mock_transport(handler=handler),
        )

        result = client.get("/experiments")
        assert result == {"ok": True}
        assert str(requests_made[0].url).endswith("/api/experiments")

    def test_auth_header_sent_when_token_set(self):
        requests_made: list[httpx.Request] = []

        def handler(request: httpx.Request) -> httpx.Response:
            requests_made.append(request)
            return httpx.Response(200, json={})

        client = HTTPClient("http://localhost:8001", token="my-jwt")
        client._client = httpx.Client(
            base_url="http://localhost:8001",
            transport=_mock_transport(handler=handler),
        )

        client.get("/experiments")
        assert requests_made[0].headers["Authorization"] == "Bearer my-jwt"

    def test_no_auth_header_when_no_token(self):
        requests_made: list[httpx.Request] = []

        def handler(request: httpx.Request) -> httpx.Response:
            requests_made.append(request)
            return httpx.Response(200, json={})

        client = HTTPClient("http://localhost:8001")
        client._client = httpx.Client(
            base_url="http://localhost:8001",
            transport=_mock_transport(handler=handler),
        )

        client.get("/experiments")
        assert "Authorization" not in requests_made[0].headers

    def test_set_token_updates_header(self):
        requests_made: list[httpx.Request] = []

        def handler(request: httpx.Request) -> httpx.Response:
            requests_made.append(request)
            return httpx.Response(200, json={})

        client = HTTPClient("http://localhost:8001")
        client._client = httpx.Client(
            base_url="http://localhost:8001",
            transport=_mock_transport(handler=handler),
        )

        client.set_token("new-token")
        client.get("/test")
        assert requests_made[0].headers["Authorization"] == "Bearer new-token"


class TestHTTPClientErrorHandling:
    def test_404_raises_not_found(self):
        client = HTTPClient("http://localhost:8001")
        client._client = httpx.Client(
            base_url="http://localhost:8001",
            transport=_mock_transport(handler=lambda r: httpx.Response(404, json={"detail": "gone"})),
        )

        with pytest.raises(NotFoundError) as exc_info:
            client.get("/experiments/999")
        assert exc_info.value.message == "gone"

    def test_500_raises_server_error(self):
        client = HTTPClient("http://localhost:8001")
        client._client = httpx.Client(
            base_url="http://localhost:8001",
            transport=_mock_transport(handler=lambda r: httpx.Response(500, json={"detail": "boom"})),
        )

        with pytest.raises(ServerError):
            client.get("/health")

    def test_connection_error(self):
        client = HTTPClient("http://localhost:8001")
        client._client = httpx.Client(
            base_url="http://localhost:8001",
            transport=_mock_transport(handler=lambda r: (_ for _ in ()).throw(httpx.ConnectError("refused"))),
        )

        with pytest.raises(MLDConnectionError):
            client.get("/health")


class TestHTTPClientTokenRefresh:
    def test_401_triggers_refresh_callback(self):
        call_count = 0

        def handler(request: httpx.Request) -> httpx.Response:
            nonlocal call_count
            call_count += 1
            if call_count == 1:
                return httpx.Response(401, json={"detail": "expired"})
            return httpx.Response(200, json={"refreshed": True})

        client = HTTPClient("http://localhost:8001", token="old-token")
        client._client = httpx.Client(
            base_url="http://localhost:8001",
            transport=_mock_transport(handler=handler),
        )
        client.set_refresh_callback(lambda: "new-token")

        result = client.get("/experiments")
        assert result == {"refreshed": True}
        assert client._token == "new-token"

    def test_401_without_refresh_callback_raises(self):
        client = HTTPClient("http://localhost:8001", token="bad-token")
        client._client = httpx.Client(
            base_url="http://localhost:8001",
            transport=_mock_transport(handler=lambda r: httpx.Response(401, json={"detail": "unauthorized"})),
        )

        with pytest.raises(AuthenticationError):
            client.get("/experiments")


class TestHTTPClientMethods:
    def _make_client(self, handler) -> HTTPClient:
        client = HTTPClient("http://localhost:8001")
        client._client = httpx.Client(
            base_url="http://localhost:8001",
            transport=_mock_transport(handler=handler),
        )
        return client

    def test_post_sends_json_body(self):
        requests_made: list[httpx.Request] = []

        def handler(request: httpx.Request) -> httpx.Response:
            requests_made.append(request)
            return httpx.Response(201, json={"id": 1})

        client = self._make_client(handler)
        result = client.post("/experiments", json={"name": "EXP-001"})

        assert result == {"id": 1}
        assert requests_made[0].method == "POST"
        body = json.loads(requests_made[0].content)
        assert body["name"] == "EXP-001"

    def test_put_method(self):
        requests_made: list[httpx.Request] = []

        def handler(request: httpx.Request) -> httpx.Response:
            requests_made.append(request)
            return httpx.Response(200, json={"updated": True})

        client = self._make_client(handler)
        client.put("/experiments/1", json={"status": "completed"})
        assert requests_made[0].method == "PUT"

    def test_patch_method(self):
        requests_made: list[httpx.Request] = []

        def handler(request: httpx.Request) -> httpx.Response:
            requests_made.append(request)
            return httpx.Response(200, json={})

        client = self._make_client(handler)
        client.patch("/experiments/1", json={"notes": "updated"})
        assert requests_made[0].method == "PATCH"

    def test_delete_method(self):
        requests_made: list[httpx.Request] = []

        def handler(request: httpx.Request) -> httpx.Response:
            requests_made.append(request)
            return httpx.Response(204, content=b"")

        client = self._make_client(handler)
        client.delete("/experiments/1")
        assert requests_made[0].method == "DELETE"

    def test_get_passes_query_params(self):
        requests_made: list[httpx.Request] = []

        def handler(request: httpx.Request) -> httpx.Response:
            requests_made.append(request)
            return httpx.Response(200, json=[])

        client = self._make_client(handler)
        client.get("/experiments", status="completed", limit=10)

        url = str(requests_made[0].url)
        assert "status=completed" in url
        assert "limit=10" in url

    def test_none_params_filtered_out(self):
        requests_made: list[httpx.Request] = []

        def handler(request: httpx.Request) -> httpx.Response:
            requests_made.append(request)
            return httpx.Response(200, json=[])

        client = self._make_client(handler)
        client.get("/experiments", status=None, limit=10)

        url = str(requests_made[0].url)
        assert "status" not in url
        assert "limit=10" in url
