# Standard Library
from unittest.mock import Mock, patch

# Django
from django.test import RequestFactory

# Alliance Auth (Core)
from allianceauth.eveonline.models import EveCharacter
from allianceauth.utils.testing import NoSocketsTestCase

from incursions.api import search as search_api


class FakeAPI:
    def __init__(self) -> None:
        self.routes: dict[tuple[str, str], object] = {}

    def get(self, path: str, **kwargs: object):
        def decorator(func):
            self.routes[("GET", path)] = func
            return func
        return decorator

    def post(self, path: str, **kwargs: object):
        def decorator(func):
            self.routes[("POST", path)] = func
            return func
        return decorator


def _routes() -> dict[tuple[str, str], object]:
    api = FakeAPI()
    search_api.setup(api)
    return api.routes


def _user(**perms: bool) -> Mock:
    return Mock(has_perm=Mock(side_effect=lambda perm: perms.get(perm, False)))


class SearchAPITestBase(NoSocketsTestCase):
    def setUp(self) -> None:
        self.factory = RequestFactory()
        self.routes = _routes()


class TestQuery(SearchAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes[("GET", "/search")]

    def test_denies_without_permission(self) -> None:
        request = self.factory.get("/search")
        request.user = _user()

        response = self.view(request, query="pilot")

        self.assertEqual(response, (403, {"error": "Permission denied"}))

    def test_returns_matching_characters(self) -> None:
        EveCharacter.objects.create(character_id=1001, character_name="Sample Pilot")
        EveCharacter.objects.create(character_id=1002, character_name="Someone Else")
        request = self.factory.get("/search")
        request.user = _user(**{"incursions.waitlist_search": True})

        response = self.view(request, query="Pilot")

        self.assertEqual(len(response.results), 1)
        self.assertEqual(response.results[0].character_name, "Sample Pilot")

    def test_allows_esi_search_permission_alone(self) -> None:
        request = self.factory.get("/search")
        request.user = _user(**{"incursions.waitlist_esi_search": True})

        response = self.view(request, query="nobody")

        self.assertEqual(response.results, [])


class TestEsiSearch(SearchAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes[("POST", "/search")]

    def test_denies_without_permission(self) -> None:
        request = self.factory.post("/search")
        request.user = _user()

        response = self.view(request, search_api.EsiSearchRequest(search="pilot"))

        self.assertEqual(response, (403, {"error": "Permission denied"}))

    @patch("incursions.api.search.search_esi")
    def test_returns_character_search_results(self, mock_search_esi: Mock) -> None:
        mock_search_esi.return_value = ({"character": [1001, 1002]}, Mock())
        request = self.factory.post("/search")
        request.user = _user(**{"incursions.waitlist_esi_search": True})
        request.user.main_character = Mock(character_id=9999)

        response = self.view(request, search_api.EsiSearchRequest(search="pilot", category="character", strict=True))

        self.assertEqual(response, [1001, 1002])
        mock_search_esi.assert_called_once_with(9999, "pilot", "character", True)

    def test_rejects_unsupported_category(self) -> None:
        request = self.factory.post("/search")
        request.user = _user(**{"incursions.waitlist_esi_search": True})
        request.user.main_character = Mock(character_id=9999)

        response = self.view(request, search_api.EsiSearchRequest(search="pilot", category="corporation"))

        self.assertEqual(response, (403, {"error": "Unsupported search category"}))
