# 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 badges as badges_api
from incursions.models.waitlist import Badge, CharacterBadges


class FakeAPI:
    """Keys routes by (method, path): badges.py registers GET and POST on the
    same '/badges/{badge_id}/members' path, so a path-only key would silently
    let the second registration overwrite the first.
    """

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

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

    def get(self, path: str, **kwargs: object):
        return self._register("GET", path, **kwargs)

    def post(self, path: str, **kwargs: object):
        return self._register("POST", path, **kwargs)

    def patch(self, path: str, **kwargs: object):
        return self._register("PATCH", path, **kwargs)

    def delete(self, path: str, **kwargs: object):
        return self._register("DELETE", path, **kwargs)


def _routes() -> dict[tuple[str, str], object]:
    api = FakeAPI()
    badges_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 BadgesAPITestBase(NoSocketsTestCase):
    def setUp(self) -> None:
        self.factory = RequestFactory()
        self.routes = _routes()
        self.granter = EveCharacter.objects.create(character_id=9500, character_name="Granter")


class TestListBadges(BadgesAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes[("GET", "/badges")]

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

        response = self.view(request)

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

    def test_lists_badges_with_member_counts(self) -> None:
        badge = Badge.objects.create(name="FC", letter="F", order=0)
        pilot = EveCharacter.objects.create(character_id=9600, character_name="Pilot")
        CharacterBadges.objects.create(character=pilot, badge=badge, granted_by=self.granter)
        request = self.factory.get("/badges")
        request.user = _user(**{"incursions.basic_waitlist": True})

        status_code, result = self.view(request)

        self.assertEqual(status_code, 200)
        self.assertEqual(len(result), 1)
        self.assertEqual(result[0].name, "FC")
        self.assertEqual(result[0].member_count, 1)


class TestGetBadgeMembers(BadgesAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes[("GET", "/badges/{badge_id}/members")]
        self.badge = Badge.objects.create(name="LOGI", letter="L", order=1)

    def test_denies_without_permission(self) -> None:
        request = self.factory.get(f"/badges/{self.badge.pk}/members")
        request.user = _user()

        response = self.view(request, self.badge.pk)

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

    def test_returns_404_for_unknown_badge(self) -> None:
        request = self.factory.get("/badges/999999/members")
        request.user = _user(**{"incursions.waitlist_badges_view": True})

        response = self.view(request, 999999)

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

    def test_returns_members_of_badge(self) -> None:
        pilot = EveCharacter.objects.create(character_id=9700, character_name="Pilot")
        CharacterBadges.objects.create(character=pilot, badge=self.badge, granted_by=self.granter)
        request = self.factory.get(f"/badges/{self.badge.pk}/members")
        request.user = _user(**{"incursions.waitlist_badges_manage": True})

        status_code, result = self.view(request, self.badge.pk)

        self.assertEqual(status_code, 200)
        self.assertEqual(len(result), 1)
        self.assertEqual(result[0].character.character_id, pilot.character_id)


class TestAssignBadge(BadgesAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes[("POST", "/badges/{badge_id}/members")]
        self.badge = Badge.objects.create(name="LOGI", letter="L", order=1)
        self.pilot = EveCharacter.objects.create(character_id=9800, character_name="Pilot")

    def test_denies_without_permission(self) -> None:
        request = self.factory.post(f"/badges/{self.badge.pk}/members")
        request.user = _user()

        response = self.view(request, self.badge.pk, badges_api.AssignBadgeRequest(character_id=self.pilot.character_id))

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

    def test_returns_404_for_unknown_character(self) -> None:
        request = self.factory.post(f"/badges/{self.badge.pk}/members")
        request.user = _user(**{"incursions.waitlist_badges_manage": True})

        response = self.view(request, self.badge.pk, badges_api.AssignBadgeRequest(character_id=999999))

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

    def test_returns_404_for_unknown_badge(self) -> None:
        request = self.factory.post("/badges/999999/members")
        request.user = _user(**{"incursions.waitlist_badges_manage": True})

        response = self.view(request, 999999, badges_api.AssignBadgeRequest(character_id=self.pilot.character_id))

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

    @patch("incursions.api.badges.get_main_character_from_user")
    def test_returns_400_when_already_assigned(self, mock_main: Mock) -> None:
        mock_main.return_value = self.granter
        CharacterBadges.objects.create(character=self.pilot, badge=self.badge, granted_by=self.granter)
        request = self.factory.post(f"/badges/{self.badge.pk}/members")
        request.user = _user(**{"incursions.waitlist_badges_manage": True})

        response = self.view(request, self.badge.pk, badges_api.AssignBadgeRequest(character_id=self.pilot.character_id))

        self.assertEqual(response, (400, {"error": "EveCharacter already has this badge"}))

    @patch("incursions.api.badges.get_main_character_from_user")
    def test_assigns_badge(self, mock_main: Mock) -> None:
        mock_main.return_value = self.granter
        request = self.factory.post(f"/badges/{self.badge.pk}/members")
        request.user = _user(**{"incursions.waitlist_badges_manage": True})

        response = self.view(request, self.badge.pk, badges_api.AssignBadgeRequest(character_id=self.pilot.character_id))

        self.assertEqual(response, (200, {"status": "Badge assigned"}))
        self.assertTrue(CharacterBadges.objects.filter(character=self.pilot, badge=self.badge).exists())


class TestRevokeBadge(BadgesAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes[("DELETE", "/badges/{badge_id}/members/{character_id}")]
        self.badge = Badge.objects.create(name="LOGI", letter="L", order=1)
        self.pilot = EveCharacter.objects.create(character_id=9900, character_name="Pilot")

    def test_denies_without_permission(self) -> None:
        request = self.factory.delete(f"/badges/{self.badge.pk}/members/{self.pilot.character_id}")
        request.user = _user()

        response = self.view(request, self.badge.pk, self.pilot.character_id)

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

    def test_returns_404_for_unknown_character(self) -> None:
        request = self.factory.delete(f"/badges/{self.badge.pk}/members/999999")
        request.user = _user(**{"incursions.waitlist_badges_manage": True})

        response = self.view(request, self.badge.pk, 999999)

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

    def test_returns_404_for_unknown_badge(self) -> None:
        request = self.factory.delete(f"/badges/999999/members/{self.pilot.character_id}")
        request.user = _user(**{"incursions.waitlist_badges_manage": True})

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

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

    def test_returns_404_when_assignment_missing(self) -> None:
        request = self.factory.delete(f"/badges/{self.badge.pk}/members/{self.pilot.character_id}")
        request.user = _user(**{"incursions.waitlist_badges_manage": True})

        response = self.view(request, self.badge.pk, self.pilot.character_id)

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

    def test_revokes_badge(self) -> None:
        CharacterBadges.objects.create(character=self.pilot, badge=self.badge, granted_by=self.granter)
        request = self.factory.delete(f"/badges/{self.badge.pk}/members/{self.pilot.character_id}")
        request.user = _user(**{"incursions.waitlist_badges_manage": True})

        response = self.view(request, self.badge.pk, self.pilot.character_id)

        self.assertEqual(response, (200, {"status": "Badge revoked"}))
        self.assertFalse(CharacterBadges.objects.filter(character=self.pilot, badge=self.badge).exists())
