"""Tests for the synchronous PresentationsAI client."""

from __future__ import annotations

import json
from typing import Any

import httpx
import pytest
import respx

from presentations_ai import (
    AuthenticationError,
    BadRequestError,
    InsufficientCreditsError,
    InternalServerError,
    NotFoundError,
    PresentationsAI,
    RateLimitError,
)
from presentations_ai.types import (
    AsyncJobResponse,
    ContentDocumentResponse,
    PresentationResponse,
    SlideUpdateResponse,
)

TEST_API_KEY = "pai_test_key"
BASE = "https://developers.presentations.ai"


class TestClientConstructor:
    def test_requires_api_key(self) -> None:
        with pytest.raises(AuthenticationError, match="API key is required"):
            PresentationsAI(api_key="")

    def test_accepts_api_key(self) -> None:
        client = PresentationsAI(api_key=TEST_API_KEY)
        assert client._api_key == TEST_API_KEY
        client.close()

    def test_env_var_fallback(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("PRESENTATIONS_AI_API_KEY", "pai_from_env")
        client = PresentationsAI()
        assert client._api_key == "pai_from_env"
        client.close()

    def test_custom_base_url(self) -> None:
        client = PresentationsAI(
            api_key=TEST_API_KEY, base_url="https://custom.api.com/"
        )
        assert client._base_url == "https://custom.api.com"
        client.close()

    def test_context_manager(self) -> None:
        with PresentationsAI(api_key=TEST_API_KEY) as client:
            assert client._api_key == TEST_API_KEY


class TestCreateFromTopic:
    @respx.mock
    def test_success(
        self,
        respx_mock: respx.MockRouter,
        presentation_response_data: dict[str, Any],
    ) -> None:
        respx_mock.post(f"{BASE}/api/v1/topic/document").mock(
            return_value=httpx.Response(200, json=presentation_response_data)
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            result = client.create_from_topic(
                topic="AI Strategy", export_type="pptx"
            )

        assert isinstance(result, PresentationResponse)
        assert result.docid == 12345
        assert result.docurl == "https://app.presentations.ai/view/abc123"

    @respx.mock
    def test_returns_async_job(
        self,
        respx_mock: respx.MockRouter,
        async_job_response_data: dict[str, Any],
    ) -> None:
        respx_mock.post(f"{BASE}/api/v1/topic/document").mock(
            return_value=httpx.Response(200, json=async_job_response_data)
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            result = client.create_from_topic(
                topic="AI Strategy", export_type="pptx"
            )

        assert isinstance(result, AsyncJobResponse)
        assert result.job_id == "job_abc123"

    def test_validates_empty_topic(self) -> None:
        with (
            PresentationsAI(api_key=TEST_API_KEY) as client,
            pytest.raises(
                BadRequestError, match="topic must be a non-empty string"
            ),
        ):
            client.create_from_topic(topic="", export_type="pptx")

    def test_validates_slide_count_range(self) -> None:
        with PresentationsAI(api_key=TEST_API_KEY) as client:
            with pytest.raises(
                BadRequestError, match="slide_count must be an integer"
            ):
                client.create_from_topic(
                    topic="test", export_type="pptx", slide_count=0
                )

            with pytest.raises(
                BadRequestError, match="slide_count must be an integer"
            ):
                client.create_from_topic(
                    topic="test", export_type="pptx", slide_count=51
                )

    @respx.mock
    def test_sends_camel_case_keys(
        self, respx_mock: respx.MockRouter
    ) -> None:
        route = respx_mock.post(f"{BASE}/api/v1/topic/document").mock(
            return_value=httpx.Response(
                200, json={"status": 0, "docid": 1, "docurl": "http://x"}
            )
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            client.create_from_topic(
                topic="test",
                export_type="pptx",
                slide_count=5,
                target_audience="VCs",
            )

        body = route.calls[0].request.content
        parsed = json.loads(body)
        assert "slideCount" in parsed
        assert "targetAudience" in parsed
        assert "exportType" in parsed


class TestCreateFromFile:
    @respx.mock
    def test_success(
        self,
        respx_mock: respx.MockRouter,
        presentation_response_data: dict[str, Any],
    ) -> None:
        respx_mock.post(f"{BASE}/api/v1/document/file").mock(
            return_value=httpx.Response(200, json=presentation_response_data)
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            result = client.create_from_file(
                file=b"PDF content here",
                file_name="report.pdf",
                export_type="pptx",
            )

        assert isinstance(result, PresentationResponse)


class TestCreateSingleSlide:
    @respx.mock
    def test_success(
        self,
        respx_mock: respx.MockRouter,
        presentation_response_data: dict[str, Any],
    ) -> None:
        respx_mock.post(f"{BASE}/api/v1/topic/singleslide").mock(
            return_value=httpx.Response(200, json=presentation_response_data)
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            result = client.create_single_slide(
                topic="Title Slide", export_type="pptx"
            )

        assert isinstance(result, PresentationResponse)


class TestCreateFromContent:
    @respx.mock
    def test_success(
        self,
        respx_mock: respx.MockRouter,
        content_document_response_data: dict[str, Any],
    ) -> None:
        respx_mock.post(f"{BASE}/api/v1/content/document").mock(
            return_value=httpx.Response(
                200, json=content_document_response_data
            )
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            result = client.create_from_content(
                name="Q4 Report",
                slides=[
                    {"title": "Slide 1", "content": "Content 1"},
                    {"title": "Slide 2", "content": "Content 2"},
                ],
            )

        assert isinstance(result, ContentDocumentResponse)
        assert result.insertid == 67890

    @respx.mock
    def test_stringifies_slides(
        self, respx_mock: respx.MockRouter
    ) -> None:
        route = respx_mock.post(f"{BASE}/api/v1/content/document").mock(
            return_value=httpx.Response(
                200,
                json={
                    "status": 0,
                    "message": "ok",
                    "docurl": "http://x",
                    "insertid": 1,
                },
            )
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            client.create_from_content(
                name="test",
                slides=[{"title": "T", "content": "C"}],
            )

        body = json.loads(route.calls[0].request.content)
        # slides should be a JSON string, not a list
        assert isinstance(body["slides"], str)
        parsed_slides = json.loads(body["slides"])
        assert parsed_slides == [{"title": "T", "content": "C"}]


class TestCreateFromRawContent:
    def test_validates_empty_content(self) -> None:
        with (
            PresentationsAI(api_key=TEST_API_KEY) as client,
            pytest.raises(
                BadRequestError,
                match="content must be a non-empty string",
            ),
        ):
            client.create_from_raw_content(content="", export_type="pptx")

    @respx.mock
    def test_success(
        self,
        respx_mock: respx.MockRouter,
        presentation_response_data: dict[str, Any],
    ) -> None:
        respx_mock.post(f"{BASE}/api/v1/document/content").mock(
            return_value=httpx.Response(200, json=presentation_response_data)
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            result = client.create_from_raw_content(
                content="Meeting notes from Q4 review...",
                export_type="pptx",
                preservation_mode="summarize",
            )

        assert isinstance(result, PresentationResponse)


class TestUpdateSlides:
    @respx.mock
    def test_success(self, respx_mock: respx.MockRouter) -> None:
        respx_mock.post(f"{BASE}/api/v1/document/slide").mock(
            return_value=httpx.Response(
                200, json={"status": 0, "message": "Updated"}
            )
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            result = client.update_slides(
                doc_id=12345,
                slides=[
                    {
                        "action": "update",
                        "slide_content": "New content",
                        "index": 0,
                    }
                ],
            )

        assert isinstance(result, SlideUpdateResponse)
        assert result.message == "Updated"

    @respx.mock
    def test_stringifies_slides(
        self, respx_mock: respx.MockRouter
    ) -> None:
        route = respx_mock.post(f"{BASE}/api/v1/document/slide").mock(
            return_value=httpx.Response(
                200, json={"status": 0, "message": "ok"}
            )
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            client.update_slides(
                doc_id=1,
                slides=[
                    {"action": "update", "slide_content": "X", "index": 0}
                ],
            )

        body = json.loads(route.calls[0].request.content)
        assert isinstance(body["slides"], str)


class TestCheckJobStatus:
    @respx.mock
    def test_success(
        self,
        respx_mock: respx.MockRouter,
        job_status_complete_data: dict[str, Any],
    ) -> None:
        respx_mock.get(f"{BASE}/api/v1/polljob/job_abc123").mock(
            return_value=httpx.Response(200, json=job_status_complete_data)
        )

        with PresentationsAI(api_key=TEST_API_KEY) as client:
            result = client.check_job_status("job_abc123")

        assert result.status == 0
        assert result.docid == 12345


class TestHttpErrorMapping:
    @respx.mock
    def test_401_raises_authentication_error(
        self, respx_mock: respx.MockRouter
    ) -> None:
        respx_mock.post(f"{BASE}/api/v1/topic/document").mock(
            return_value=httpx.Response(
                401, json={"error": "Invalid API key"}
            )
        )

        with PresentationsAI(
            api_key=TEST_API_KEY, max_retries=0
        ) as client:
            with pytest.raises(AuthenticationError) as exc_info:
                client.create_from_topic(topic="test", export_type="pptx")
            assert exc_info.value.status_code == 401
            assert exc_info.value.code == "API_UNAUTHORIZED"

    @respx.mock
    def test_402_raises_insufficient_credits(
        self, respx_mock: respx.MockRouter
    ) -> None:
        respx_mock.post(f"{BASE}/api/v1/topic/document").mock(
            return_value=httpx.Response(
                402, json={"error": "Out of credits"}
            )
        )

        with PresentationsAI(
            api_key=TEST_API_KEY, max_retries=0
        ) as client:
            with pytest.raises(InsufficientCreditsError) as exc_info:
                client.create_from_topic(topic="test", export_type="pptx")
            assert exc_info.value.status_code == 402

    @respx.mock
    def test_404_raises_not_found(
        self, respx_mock: respx.MockRouter
    ) -> None:
        respx_mock.get(f"{BASE}/api/v1/polljob/nonexistent").mock(
            return_value=httpx.Response(404, json={"error": "Not found"})
        )

        with (
            PresentationsAI(
                api_key=TEST_API_KEY, max_retries=0
            ) as client,
            pytest.raises(NotFoundError),
        ):
            client.check_job_status("nonexistent")

    @respx.mock
    def test_429_raises_rate_limit(
        self, respx_mock: respx.MockRouter
    ) -> None:
        respx_mock.post(f"{BASE}/api/v1/topic/document").mock(
            return_value=httpx.Response(
                429, json={"error": "Too many requests"}
            )
        )

        with (
            PresentationsAI(
                api_key=TEST_API_KEY, max_retries=0
            ) as client,
            pytest.raises(RateLimitError),
        ):
            client.create_from_topic(topic="test", export_type="pptx")

    @respx.mock
    def test_500_raises_internal_server_error(
        self, respx_mock: respx.MockRouter
    ) -> None:
        respx_mock.post(f"{BASE}/api/v1/topic/document").mock(
            return_value=httpx.Response(500, text="Internal Server Error")
        )

        with (
            PresentationsAI(
                api_key=TEST_API_KEY, max_retries=0
            ) as client,
            pytest.raises(InternalServerError),
        ):
            client.create_from_topic(topic="test", export_type="pptx")
