# pyright: reportPrivateUsage=false
"""Mock tests: tasks resource."""

from typing import Any
from unittest.mock import AsyncMock, patch

import pytest

from seaart import AsyncClient
from seaart._internal._schemas.envelope import APIEnvelope
from seaart.exceptions import MissingDataError, TaskTimeoutError

from ._fixtures import (
    ARTWORK_DETAILS,
    TASK_ITEM,
    TASK_ITEM_PENDING,
    TASK_OPERATION_RESULT,
)
from .conftest import make_ok as _ok


# ── batch_progress ──────────────────────────────────────────────────────────


class TestBatchProgress:
    async def test_returns_items(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({"items": [TASK_ITEM]})):
            result = await client.tasks.batch_progress(["t1"])
            assert result.status.code == 10000
            assert len(result.value.items) == 1
            assert result.value.items[0].task_id == "t1"

    async def test_sends_correct_payload(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({"items": [TASK_ITEM]})) as mock:
            await client.tasks.batch_progress(["t1", "t2"])
            assert mock.call_args.kwargs["json"]["task_ids"] == ["t1", "t2"]


# ── progress (single task) ──────────────────────────────────────────────────


class TestProgress:
    async def test_returns_single_item(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({"items": [TASK_ITEM]})):
            result = await client.tasks.progress("t1")
            assert result.value.task_id == "t1"

    async def test_raises_when_task_not_found(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({"items": []})):
            with pytest.raises(MissingDataError, match="t1"):
                await client.tasks.progress("t1")


# ── cancel ──────────────────────────────────────────────────────────────────


class TestCancel:
    async def test_cancel(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(TASK_OPERATION_RESULT)):
            result = await client.tasks.cancel("t1")
            assert result.status.code == 10000

    async def test_cancel_sends_action_5(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(TASK_OPERATION_RESULT)) as mock:
            await client.tasks.cancel("t1")
            body: dict[str, Any] = mock.call_args.kwargs["json"]
            assert body["action"] == 5
            assert body["task_id"] == "t1"


# ── delete ──────────────────────────────────────────────────────────────────


class TestDelete:
    async def test_delete_single(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok()) as mock:
            await client.tasks.delete("t1")
            assert mock.call_args.kwargs["json"]["ids"] == ["t1"]

    async def test_delete_list(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok()) as mock:
            await client.tasks.delete(["t1", "t2"])
            assert mock.call_args.kwargs["json"]["ids"] == ["t1", "t2"]


# ── speed_up ────────────────────────────────────────────────────────────────


class TestSpeedUp:
    async def test_speed_up(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({"num": 1})):
            result = await client.tasks.speed_up("t1")
            assert result.status.code == 10000


# ── detail ──────────────────────────────────────────────────────────────────


class TestDetail:
    async def test_detail(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok(ARTWORK_DETAILS)):
            result = await client.tasks.detail("t1")
            assert result.status.code == 10000


# ── wait ────────────────────────────────────────────────────────────────────


class TestWait:
    async def test_wait_returns_immediately_when_done(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({"items": [TASK_ITEM]})):
            item = await client.tasks.wait("t1", interval=0.01)
            assert item.task_id == "t1"

    async def test_wait_polls_until_done(self, client: AsyncClient) -> None:
        call_count = 0

        async def fake_request(*_a: Any, **_k: Any) -> APIEnvelope[object]:
            nonlocal call_count
            call_count += 1
            if call_count < 3:
                return _ok({"items": [TASK_ITEM_PENDING]})
            return _ok({"items": [TASK_ITEM]})

        with patch.object(client, "request_route", side_effect=fake_request):
            item = await client.tasks.wait("t1", interval=0.01)
            assert item.task_id == "t1"
            assert call_count == 3

    async def test_wait_timeout(self, client: AsyncClient) -> None:
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({"items": [TASK_ITEM_PENDING]})):
            with pytest.raises(TaskTimeoutError):
                await client.tasks.wait("t1", interval=0.01, timeout=0.03)


# ── wait_many ───────────────────────────────────────────────────────────────


class TestWaitMany:
    async def test_empty_list(self, client: AsyncClient) -> None:
        result = await client.tasks.wait_many([])
        assert result == {}

    async def test_all_done(self, client: AsyncClient) -> None:
        item1: dict[str, Any] = {**TASK_ITEM, "task_id": "t1"}
        item2: dict[str, Any] = {**TASK_ITEM, "task_id": "t2"}
        with patch.object(client, "request_route", new_callable=AsyncMock, return_value=_ok({"items": [item1, item2]})):
            result = await client.tasks.wait_many(["t1", "t2"], interval=0.01)
            assert "t1" in result
            assert "t2" in result
