"""Tests for polling utilities."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from presentations_ai import APITimeoutError, CloudyError
from presentations_ai._polling import poll_until_complete
from presentations_ai.types.jobs import JobStatusResponse


class TestPollUntilComplete:
    def test_success_on_first_poll(self) -> None:
        mock_client = MagicMock()
        mock_client.check_job_status.return_value = JobStatusResponse(
            status=0, message="Ready", docid=123, docurl="https://example.com"
        )

        result = poll_until_complete(mock_client, "job_1")
        assert result.status == 0
        assert result.docid == 123
        mock_client.check_job_status.assert_called_once_with("job_1")

    @patch("presentations_ai._polling.time.sleep")
    def test_success_after_processing(self, mock_sleep: MagicMock) -> None:
        mock_client = MagicMock()
        mock_client.check_job_status.side_effect = [
            JobStatusResponse(status=1, message="Document is being processed"),
            JobStatusResponse(status=1, message="Document is being processed"),
            JobStatusResponse(status=0, message="Ready", docid=42, docurl="http://x"),
        ]

        result = poll_until_complete(mock_client, "job_1", interval_ms=100)
        assert result.status == 0
        assert result.docid == 42
        assert mock_client.check_job_status.call_count == 3
        assert mock_sleep.call_count == 2

    @patch("presentations_ai._polling.time.sleep")
    def test_raises_on_job_failure(self, mock_sleep: MagicMock) -> None:
        mock_client = MagicMock()
        mock_client.check_job_status.return_value = JobStatusResponse(
            status=1, message="Generation failed: invalid content"
        )

        with pytest.raises(CloudyError, match="Generation failed"):
            poll_until_complete(mock_client, "job_1")

    @patch("presentations_ai._polling.time.sleep")
    def test_raises_on_timeout(self, mock_sleep: MagicMock) -> None:
        mock_client = MagicMock()
        mock_client.check_job_status.return_value = JobStatusResponse(
            status=1, message="Document is being processed"
        )

        with pytest.raises(APITimeoutError, match="did not complete"):
            poll_until_complete(mock_client, "job_1", max_attempts=3, interval_ms=10)

        assert mock_client.check_job_status.call_count == 3

    @patch("presentations_ai._polling.time.sleep")
    def test_calls_on_progress(self, mock_sleep: MagicMock) -> None:
        mock_client = MagicMock()
        mock_client.check_job_status.side_effect = [
            JobStatusResponse(status=1, message="Document is being processed"),
            JobStatusResponse(status=0, message="Ready", docid=1, docurl="http://x"),
        ]

        progress_calls: list[JobStatusResponse] = []
        poll_until_complete(
            mock_client,
            "job_1",
            interval_ms=10,
            on_progress=lambda r: progress_calls.append(r),
        )

        assert len(progress_calls) == 2
        assert progress_calls[0].status == 1
        assert progress_calls[1].status == 0

    @patch("presentations_ai._polling.time.sleep")
    def test_backoff_multiplier(self, mock_sleep: MagicMock) -> None:
        mock_client = MagicMock()
        mock_client.check_job_status.side_effect = [
            JobStatusResponse(status=1, message="Document is being processed"),
            JobStatusResponse(status=1, message="Document is being processed"),
            JobStatusResponse(status=0, message="Ready", docid=1, docurl="http://x"),
        ]

        poll_until_complete(
            mock_client, "job_1", interval_ms=1000, backoff_multiplier=2.0
        )

        # First sleep: 1000ms = 1.0s, second sleep: 2000ms = 2.0s
        assert mock_sleep.call_count == 2
        assert mock_sleep.call_args_list[0].args[0] == pytest.approx(1.0)
        assert mock_sleep.call_args_list[1].args[0] == pytest.approx(2.0)
