"""Tests for markitect.llm.gemini — retry behavior + happy path."""

from unittest import mock

import pytest

from markitect.llm.gemini import GeminiAdapter
from markitect.llm.exceptions import LLMAPIError, LLMRateLimitError
from markitect.prompts.execution.models import RunConfig, LLMResponse


def _api_response(text="hello", model="gemini-2.5-flash"):
    return {
        "candidates": [
            {
                "content": {"parts": [{"text": text}], "role": "model"},
                "finishReason": "STOP",
            }
        ],
        "modelVersion": model,
        "usageMetadata": {
            "promptTokenCount": 3,
            "candidatesTokenCount": 2,
            "totalTokenCount": 5,
        },
    }


class TestGeminiAdapter:
    def _adapter(self, **kwargs):
        defaults = {"api_key": "AIza-test"}
        defaults.update(kwargs)
        return GeminiAdapter(**defaults)

    @mock.patch("markitect.llm.gemini.post_json")
    def test_success(self, mock_post):
        mock_post.return_value = _api_response("generated")
        adapter = self._adapter()
        resp = adapter.execute_prompt("hi", RunConfig())
        assert isinstance(resp, LLMResponse)
        assert resp.content == "generated"
        assert resp.metadata["provider"] == "gemini"

    @mock.patch("markitect.llm.gemini.post_json")
    @mock.patch("markitect.llm.gemini.time.sleep")
    def test_retry_on_429(self, mock_sleep, mock_post):
        mock_post.side_effect = [
            LLMRateLimitError("rate limited", status_code=429),
            _api_response("recovered"),
        ]
        adapter = self._adapter(max_retries=2)
        resp = adapter.execute_prompt("hi", RunConfig())
        assert resp.content == "recovered"
        assert mock_sleep.call_count == 1

    @mock.patch("markitect.llm.gemini.post_json")
    @mock.patch("markitect.llm.gemini.time.sleep")
    def test_retry_on_503(self, mock_sleep, mock_post):
        mock_post.side_effect = [
            LLMAPIError("unavailable", status_code=503),
            _api_response("back"),
        ]
        adapter = self._adapter(max_retries=2)
        resp = adapter.execute_prompt("hi", RunConfig())
        assert resp.content == "back"

    @mock.patch("markitect.llm.gemini.post_json")
    def test_no_retry_on_400(self, mock_post):
        mock_post.side_effect = LLMAPIError("bad request", status_code=400)
        adapter = self._adapter(max_retries=2)
        with pytest.raises(LLMAPIError) as exc_info:
            adapter.execute_prompt("hi", RunConfig())
        assert exc_info.value.status_code == 400

    @mock.patch("markitect.llm.gemini.post_json")
    @mock.patch("markitect.llm.gemini.time.sleep")
    def test_exhausted_retries_raises(self, mock_sleep, mock_post):
        mock_post.side_effect = LLMRateLimitError("rate limited", status_code=429)
        adapter = self._adapter(max_retries=1)
        with pytest.raises(LLMRateLimitError):
            adapter.execute_prompt("hi", RunConfig())
        assert mock_sleep.call_count == 1  # 1 retry before giving up
