# pyright: reportPrivateUsage=false
"""Mock tests: search, images (read-only), prompts, genagent."""

from collections.abc import AsyncIterator
from typing import Any
from unittest.mock import AsyncMock, patch

import pytest

from seaart import AsyncClient
from seaart._internal._schemas.envelope import APIEnvelope

from ._fixtures import IMAGE_DOWNLOAD, SEARCH_RESULT, UPLOADED_IMAGE_URL
from .conftest import make_ok as _ok


# ── Search ──────────────────────────────────────────────────────────────────


class TestSearchMock:
    async def test_all(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(SEARCH_RESULT)):
            result = await client.search.all("anime", page_size=5)
            assert result.status.code == 10000

    async def test_all_sends_correct_query_type(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(SEARCH_RESULT)) as mock:
            await client.search.all("test", page_size=5)
            body: dict[str, Any] = mock.call_args.kwargs["json"]
            assert body["obj_name"] == "test"
            assert body["obj_type"] == 99

    @pytest.mark.parametrize(
        ("method", "expected_type"),
        [
            ("works", 4),
            ("models", 2),
            ("characters", 9),
            ("ai_apps", 14),
            ("ai_videos", 15),
            ("workflows", 13),
        ],
    )
    async def test_search_subtypes(
        self, client: AsyncClient, method: str, expected_type: int
    ) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(SEARCH_RESULT)) as mock:
            fn = getattr(client.search, method)
            await fn("test", page_size=5)
            body: dict[str, Any] = mock.call_args.kwargs["json"]
            assert body["obj_type"] == expected_type

    async def test_search_with_order(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(SEARCH_RESULT)) as mock:
            await client.search.all("test", order_by="new")
            assert mock.call_args.kwargs["json"]["order_by"] == "new"

    async def test_search_pagination(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(SEARCH_RESULT)) as mock:
            await client.search.all("test", page=3, page_size=10)
            body: dict[str, Any] = mock.call_args.kwargs["json"]
            assert body["page"] == 3
            assert body["page_size"] == 10


# ── Images (read-only) ─────────────────────────────────────────────────────


class TestImagesReadOnlyMock:
    async def test_get_download_url(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(IMAGE_DOWNLOAD)):
            result = await client.images.get_download_url("https://example.com/img.png")
            assert result.status.code == 10000

    async def test_upload_by_url(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(UPLOADED_IMAGE_URL)):
            result = await client.images.upload_by_url("https://example.com/img.png")
            assert result.status.code == 10000

    async def test_upload_by_url_sends_correct_payload(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(UPLOADED_IMAGE_URL)) as mock:
            await client.images.upload_by_url("https://example.com/img.png")
            assert mock.call_args.kwargs["json"]["url"] == "https://example.com/img.png"


# ── Images history ──────────────────────────────────────────────────────────


class TestImagesHistoryMock:
    async def test_list(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({})):
            result = await client.images.history.list()
            assert result.status.code == 10000

    async def test_add(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok()):
            result = await client.images.history.add("https://example.com/img.png")
            assert result.status.code == 10000

    async def test_delete(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok()):
            result = await client.images.history.delete("https://example.com/img.png")
            assert result.status.code == 10000


# ── Models ──────────────────────────────────────────────────────────────────


class TestModelsMock:
    async def test_generation_parameters_payload(self, client: AsyncClient) -> None:
        """generation_parameters returns raw envelope — no extra model_validate."""
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({})) as mock:
            await client.models.generation_parameters("m1", "v1")
            body: dict[str, Any] = mock.call_args.kwargs["json"]
            assert body["model_no"] == "m1"
            assert body["model_ver_no"] == "v1"

    async def test_detail_payload(self, client: AsyncClient) -> None:
        """Verify detail sends the correct payload (ModelDetails has too many required fields)."""
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({})) as mock:
            try:
                await client.models.detail("m1")
            except Exception:
                pass  # model_validate on response may fail
            mock.assert_awaited_once()
            body: dict[str, Any] = mock.call_args.kwargs["json"]
            assert body["id"] == "m1"
            assert body["scene"] == "model_detail_v2"


# ── Prompts ─────────────────────────────────────────────────────────────────


class TestPromptsMock:
    async def test_recommend(self, client: AsyncClient) -> None:
        data: list[dict[str, Any]] = [{"prompt": "a cute cat", "weight": 1.0}]
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(data)):
            result = await client.prompts.recommend("m1")
            assert result.status.code == 10000

    async def test_stream_render(self, client: AsyncClient) -> None:
        env1 = _ok({"prompt": "enhanced cat"})
        env2 = _ok(None)

        async def fake_stream(*_a: Any, **_k: Any) -> AsyncIterator[APIEnvelope[object]]:
            yield env1
            yield env2

        with patch.object(client, "request_route_stream", side_effect=fake_stream):
            chunks: list[APIEnvelope[object]] = []
            async for env in client.prompts.stream_render("a cat"):
                chunks.append(env)
            assert len(chunks) == 2

    async def test_render_returns_prompt(self, client: AsyncClient) -> None:
        env_data = APIEnvelope[object].model_validate(
            {"status": {"code": 99, "msg": ""}, "data": {"prompt": "enhanced prompt"}}
        )
        env_done = _ok({"prompt": "done"})

        async def fake_stream(*_a: Any, **_k: Any) -> AsyncIterator[APIEnvelope[object]]:
            yield env_data
            yield env_done

        with patch.object(client, "request_route_stream", side_effect=fake_stream):
            result = await client.prompts.render("a cat")
            assert result == "enhanced prompt"

    async def test_render_returns_none_when_empty(self, client: AsyncClient) -> None:
        async def fake_stream(*_a: Any, **_k: Any) -> AsyncIterator[APIEnvelope[object]]:
            yield _ok(None)

        with patch.object(client, "request_route_stream", side_effect=fake_stream):
            result = await client.prompts.render("a cat")
            assert result is None


# ── GenAgent ────────────────────────────────────────────────────────────────


class TestGenAgentMock:
    async def test_create(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({"conv_id": "conv-1"})):
            result = await client.genagent.create()
            assert result.status.code == 10000

    async def test_delete(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok()):
            result = await client.genagent.delete("conv-1")
            assert result.status.code == 10000
