# pyright: reportPrivateUsage=false
"""Unit tests: response parsing, error mapping, and _handle_response."""

import json

import pytest

from seaart._base_client import BaseClient
from seaart.exceptions import (
    APIError,
    AccountBlockedError,
    AccountCancelOncePerDayError,
    AccountForbiddenError,
    AccountNotFoundError,
    AccountNotLoggedInError,
    ArtModelNoIsEmptyError,
    AuthTokenInvalidError,
    BalanceNotEnoughError,
    ComfyuiApiNodeCannotBeCanceledError,
    DoNotTurnOffPermissionToPublishWorksToTheCommunityError,
    FeatureRequiresPaymentError,
    ImageCountExceedLimitError,
    InvalidPasswordError,
    OperationNotAllowedError,
    ParamsError,
    PromptHaveSensitiveWordsError,
    ServerError,
    TaskCompletionCannotBeCanceledError,
    TaskHangLimitExceededError,
    TouristChatNumLimitError,
    UsernameError,
)


# ── Helpers ──────────────────────────────────────────────────────────────────


def _make_body(code: int, msg: str = "", data: object = None) -> bytes:
    return json.dumps(
        {"status": {"code": code, "msg": msg, "request_id": "r1"}, "data": data}
    ).encode()


def _success_body(data: object = None) -> bytes:
    return _make_body(10000, "ok", data)


class _StubClient(BaseClient[None]):
    """Minimal concrete subclass so we can call _handle_response."""

    def _init_session(self) -> None:
        return None


@pytest.fixture()
def client() -> _StubClient:
    return _StubClient()


# ── _handle_response: happy paths ────────────────────────────────────────────


class TestHandleResponseHappy:
    def test_success_with_data(self, client: _StubClient) -> None:
        env = client._handle_response(200, _success_body({"id": "abc"}))
        assert env.status.code == 10000
        assert env.data == {"id": "abc"}

    def test_success_none_data(self, client: _StubClient) -> None:
        env = client._handle_response(200, _success_body())
        assert env.status.code == 10000
        assert env.data is None

    def test_204_no_content(self, client: _StubClient) -> None:
        env = client._handle_response(204, b"")
        assert env.status.code == 10000

    def test_205_reset_content(self, client: _StubClient) -> None:
        env = client._handle_response(205, b"")
        assert env.status.code == 10000

    def test_empty_body(self, client: _StubClient) -> None:
        env = client._handle_response(200, b"")
        assert env.status.code == 10000

    def test_bytearray_body(self, client: _StubClient) -> None:
        body = bytes(bytearray(_success_body({"x": 1})))
        env = client._handle_response(200, body)
        assert env.data == {"x": 1}


# ── _handle_response: error paths ────────────────────────────────────────────


class TestHandleResponseErrors:
    def test_http_400_raises(self, client: _StubClient) -> None:
        with pytest.raises(APIError) as exc_info:
            client._handle_response(400, b"bad request")
        assert exc_info.value.http_status == 400

    def test_http_500_raises(self, client: _StubClient) -> None:
        with pytest.raises(APIError) as exc_info:
            client._handle_response(500, b"server error")
        assert exc_info.value.http_status == 500

    def test_invalid_json_raises(self, client: _StubClient) -> None:
        with pytest.raises(APIError, match="Non-JSON response"):
            client._handle_response(200, b"not json at all")

    def test_invalid_envelope_raises(self, client: _StubClient) -> None:
        with pytest.raises(APIError, match="Invalid envelope"):
            client._handle_response(200, b'{"unexpected": "shape"}')


# ── Error code mapping ───────────────────────────────────────────────────────


class TestErrorCodeMapping:
    @pytest.mark.parametrize(
        ("code", "expected_cls"),
        [
            (10100, ParamsError),
            (10200, ServerError),
            (20000, AccountNotLoggedInError),
            (20001, AccountNotFoundError),
            (20002, AuthTokenInvalidError),
            (20004, AccountForbiddenError),
            (20005, InvalidPasswordError),
            (20009, UsernameError),
            (20602, AccountCancelOncePerDayError),
            (40011, DoNotTurnOffPermissionToPublishWorksToTheCommunityError),
            (60003, TouristChatNumLimitError),
            (70000, OperationNotAllowedError),
            (70002, TaskHangLimitExceededError),
            (70004, TaskCompletionCannotBeCanceledError),
            (70010, FeatureRequiresPaymentError),
            (70026, PromptHaveSensitiveWordsError),
            (70052, ComfyuiApiNodeCannotBeCanceledError),
            (70061, ImageCountExceedLimitError),
            (80008, BalanceNotEnoughError),
            (80318, AccountBlockedError),
        ],
    )
    def test_error_code_maps_to_correct_class(
        self, client: _StubClient, code: int, expected_cls: type[APIError]
    ) -> None:
        body = _make_body(code, "some msg")
        with pytest.raises(expected_cls):
            client._handle_response(200, body)

    def test_unknown_error_code_raises_generic(self, client: _StubClient) -> None:
        body = _make_body(99999, "unknown")
        with pytest.raises(APIError):
            client._handle_response(200, body)

    def test_10200_art_model_no_empty(self, client: _StubClient) -> None:
        body = _make_body(10200, "art_model_no is empty")
        with pytest.raises(ArtModelNoIsEmptyError):
            client._handle_response(200, body)

    def test_10200_generic_server_error(self, client: _StubClient) -> None:
        body = _make_body(10200, "something else broke")
        with pytest.raises(ServerError):
            client._handle_response(200, body)

    def test_error_attributes(self, client: _StubClient) -> None:
        body = _make_body(70002, "task hang limit exceeded")
        with pytest.raises(TaskHangLimitExceededError) as exc_info:
            client._handle_response(200, body)
        err = exc_info.value
        assert err.http_status == 200
        assert err.code == 70002
        assert err.msg == "task hang limit exceeded"


# ── _resolve_error_class edge cases ──────────────────────────────────────────


class TestResolveErrorClass:
    def test_none_code(self, client: _StubClient) -> None:
        assert client._resolve_error_class(None, None) is APIError

    def test_unknown_code(self, client: _StubClient) -> None:
        assert client._resolve_error_class(12345, "x") is APIError

    def test_10200_with_art_model_no(self, client: _StubClient) -> None:
        assert (
            client._resolve_error_class(10200, "art_model_no is empty")
            is ArtModelNoIsEmptyError
        )

    def test_10200_without_art_model_no(self, client: _StubClient) -> None:
        assert client._resolve_error_class(10200, "generic error") is ServerError

    def test_10200_none_msg(self, client: _StubClient) -> None:
        assert client._resolve_error_class(10200, None) is ServerError
