# Standard Library
from unittest.mock import Mock, patch

# Third Party
from aadiscordbot.cogs.utils.exceptions import NotAuthenticated

# Django
from django.http import Http404
from django.test import RequestFactory

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

from incursions.api import pilot as pilot_api
from incursions.models.waitlist import Badge, Ban, CharacterBadges


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

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


def _routes() -> dict[str, object]:
    api = FakeAPI()
    pilot_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 PilotAPITestBase(NoSocketsTestCase):
    def setUp(self) -> None:
        self.factory = RequestFactory()
        self.routes = _routes()
        self.character = EveCharacter.objects.create(character_id=1001, character_name="Pilot One")


class TestPilotInfo(PilotAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes["/pilot/info"]

    @patch("incursions.api.pilot.get_all_characters_from_user", return_value=[])
    def test_denies_without_permission_or_ownership(self, mock_alts: Mock) -> None:
        request = self.factory.get("/pilot/info")
        request.user = _user()

        response = self.view(request, character_id=self.character.character_id)

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

    @patch("incursions.api.pilot.get_all_characters_from_user", return_value=[])
    def test_returns_404_for_missing_character(self, mock_alts: Mock) -> None:
        request = self.factory.get("/pilot/info")
        request.user = _user(**{"incursions.waitlist_pilot_view": True})

        response = self.view(request, character_id=999999)

        self.assertEqual(response, (404, {"error": "Pilot not found"}))

    @patch("incursions.api.pilot.get_all_characters_from_user", return_value=[])
    def test_returns_badges_and_active_bans(self, mock_alts: Mock) -> None:
        badge = Badge.objects.create(name="Sponge", letter="S", order=1)
        CharacterBadges.objects.create(character=self.character, badge=badge)
        Ban.objects.create(entity_type=Ban.entity_choices.CHARACTER, entity_character=self.character, reason="test", issued_by=self.character)
        request = self.factory.get("/pilot/info")
        request.user = _user(**{"incursions.waitlist_pilot_view": True})

        status_code, body = self.view(request, character_id=self.character.character_id)

        self.assertEqual(status_code, 200)
        self.assertEqual(body.character.character_id, self.character.character_id)
        self.assertEqual(len(body.badges), 1)
        self.assertEqual(body.badges[0].name, "Sponge")
        self.assertEqual(len(body.active_bans), 1)

    @patch("incursions.api.pilot.get_all_characters_from_user")
    def test_allows_own_character_without_permission(self, mock_alts: Mock) -> None:
        mock_alts.return_value = [self.character]
        request = self.factory.get("/pilot/info")
        request.user = _user()

        status_code, body = self.view(request, character_id=self.character.character_id)

        self.assertEqual(status_code, 200)


class TestPilotDiscord(PilotAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes["/pilot/discord"]

    @patch("incursions.api.pilot.get_all_characters_from_user", return_value=[])
    def test_denies_without_permission_or_ownership(self, mock_alts: Mock) -> None:
        request = self.factory.get("/pilot/discord")
        request.user = _user()

        response = self.view(request, character_id=self.character.character_id)

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

    @patch("incursions.api.pilot.get_all_characters_from_user", return_value=[])
    def test_returns_404_for_missing_character(self, mock_alts: Mock) -> None:
        request = self.factory.get("/pilot/discord")
        request.user = _user(**{"incursions.waitlist_pilot_view": True})

        response = self.view(request, character_id=999999)

        self.assertEqual(response, (404, {"error": "Pilot not found"}))

    @patch("incursions.api.pilot.get_all_characters_from_user", return_value=[])
    @patch("incursions.api.pilot.get_user_from_evecharacter")
    @patch("incursions.api.pilot.get_discord_user_id")
    def test_returns_404_when_discord_user_not_found(self, mock_discord_id: Mock, mock_get_user: Mock, mock_alts: Mock) -> None:
        mock_discord_id.side_effect = NotAuthenticated()
        request = self.factory.get("/pilot/discord")
        request.user = _user(**{"incursions.waitlist_pilot_view": True})

        response = self.view(request, character_id=self.character.character_id)

        self.assertEqual(response, (404, {"error": "Discord user not found"}))

    @patch("incursions.api.pilot.get_all_characters_from_user", return_value=[])
    @patch("incursions.api.pilot.get_user_from_evecharacter")
    @patch("incursions.api.pilot.get_discord_user_id")
    def test_returns_discord_username(self, mock_discord_id: Mock, mock_get_user: Mock, mock_alts: Mock) -> None:
        mock_discord_id.return_value = 123456789
        request = self.factory.get("/pilot/discord")
        request.user = _user(**{"incursions.waitlist_pilot_view": True})

        status_code, body = self.view(request, character_id=self.character.character_id)

        self.assertEqual(status_code, 200)
        self.assertEqual(body.discord_username, "<@123456789>")


class TestAltInfo(PilotAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes["/pilot/alts"]

    @patch("incursions.api.pilot.get_all_characters_from_user", return_value=[])
    @patch("incursions.api.pilot.get_user_from_evecharacter")
    def test_denies_without_permission_or_ownership(self, mock_get_user: Mock, mock_alts: Mock) -> None:
        request = self.factory.get("/pilot/alts")
        request.user = _user()

        response = self.view(request, character_id=self.character.character_id)

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

    def test_returns_404_for_missing_character(self) -> None:
        request = self.factory.get("/pilot/alts")
        request.user = _user(**{"incursions.waitlist_alts_view": True})

        with self.assertRaises(Http404):
            self.view(request, character_id=999999)

    @patch("incursions.api.pilot.get_all_characters_from_user")
    @patch("incursions.api.pilot.get_user_from_evecharacter")
    def test_returns_alts_excluding_self(self, mock_get_user: Mock, mock_alts: Mock) -> None:
        alt = EveCharacter.objects.create(character_id=1002, character_name="Alt One")
        mock_get_user.return_value = Mock()
        mock_alts.return_value = [self.character, alt]
        request = self.factory.get("/pilot/alts")
        request.user = _user(**{"incursions.waitlist_alts_view": True})

        response = self.view(request, character_id=self.character.character_id)

        self.assertEqual([c.character_id for c in response], [alt.character_id])
