"""Tests for markitect.llm._http."""

import json
import urllib.error
from unittest import mock

import pytest

from markitect.llm._http import post_json
from markitect.llm.exceptions import LLMAPIError, LLMRateLimitError, LLMTimeoutError


class TestPostJson:
    def _mock_urlopen(self, response_body: dict, status: int = 200):
        """Return a context-manager mock for urllib.request.urlopen."""
        body_bytes = json.dumps(response_body).encode()
        resp = mock.MagicMock()
        resp.read.return_value = body_bytes
        resp.__enter__ = mock.MagicMock(return_value=resp)
        resp.__exit__ = mock.MagicMock(return_value=False)
        return resp

    @mock.patch("markitect.llm._http.urllib.request.urlopen")
    def test_success(self, mock_urlopen):
        expected = {"choices": [{"message": {"content": "hi"}}]}
        mock_urlopen.return_value = self._mock_urlopen(expected)
        result = post_json("https://api.test/v1", {"prompt": "hello"})
        assert result == expected

    @mock.patch("markitect.llm._http.urllib.request.urlopen")
    def test_429_raises_rate_limit(self, mock_urlopen):
        exc = urllib.error.HTTPError(
            "https://api.test/v1", 429, "Too Many Requests", {}, None
        )
        exc.read = mock.MagicMock(return_value=b"rate limited")
        mock_urlopen.side_effect = exc

        with pytest.raises(LLMRateLimitError) as exc_info:
            post_json("https://api.test/v1", {})
        assert exc_info.value.status_code == 429

    @mock.patch("markitect.llm._http.urllib.request.urlopen")
    def test_500_raises_api_error(self, mock_urlopen):
        exc = urllib.error.HTTPError(
            "https://api.test/v1", 500, "Internal Server Error", {}, None
        )
        exc.read = mock.MagicMock(return_value=b"server error")
        mock_urlopen.side_effect = exc

        with pytest.raises(LLMAPIError) as exc_info:
            post_json("https://api.test/v1", {})
        assert exc_info.value.status_code == 500

    @mock.patch("markitect.llm._http.urllib.request.urlopen")
    def test_timeout_raises_timeout_error(self, mock_urlopen):
        exc = urllib.error.URLError("timed out")
        mock_urlopen.side_effect = exc

        with pytest.raises(LLMTimeoutError):
            post_json("https://api.test/v1", {})

    @mock.patch("markitect.llm._http.urllib.request.urlopen")
    def test_generic_url_error_raises_api_error(self, mock_urlopen):
        exc = urllib.error.URLError("connection refused")
        mock_urlopen.side_effect = exc

        with pytest.raises(LLMAPIError):
            post_json("https://api.test/v1", {})

    @mock.patch("markitect.llm._http.urllib.request.urlopen")
    def test_python_timeout_error(self, mock_urlopen):
        mock_urlopen.side_effect = TimeoutError("timed out")

        with pytest.raises(LLMTimeoutError):
            post_json("https://api.test/v1", {})
