# Standard Library
from unittest.mock import Mock

# Django
from django.test import RequestFactory

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

# Alliance Auth (External Libs)
from eve_sde.models import ItemCategory, ItemGroup, ItemType
from eve_sde.models.types import DogmaEffect, TypeEffect

from incursions.api import modules as modules_api
from incursions.api.modules import SlotEnum, module_slot
from incursions.models.waitlist import ApprovedFitting


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


class ModulesFixtureTestCase(NoSocketsTestCase):
    def setUp(self) -> None:
        self.category_module = ItemCategory.objects.create(id=7, name="Module", name_en="Module", published=True)
        self.category_drone = ItemCategory.objects.create(id=18, name="Drone", name_en="Drone", published=True)
        self.category_implant = ItemCategory.objects.create(id=20, name="Implant", name_en="Implant", published=True)
        self.category_other = ItemCategory.objects.create(id=99, name="Other", name_en="Other", published=True)

        self.group_module = ItemGroup.objects.create(id=700, name="Modules", name_en="Modules", category=self.category_module, published=True)
        self.group_drone = ItemGroup.objects.create(id=701, name="Drones", name_en="Drones", category=self.category_drone, published=True)
        self.group_implant = ItemGroup.objects.create(id=702, name="Implants", name_en="Implants", category=self.category_implant, published=True)
        self.group_other = ItemGroup.objects.create(id=703, name="Other Group", name_en="Other Group", category=self.category_other, published=True)


class TestModuleSlot(ModulesFixtureTestCase):
    def _module(self, module_id: int, group: ItemGroup | None) -> ItemType:
        return ItemType.objects.create(id=module_id, name=f"Module {module_id}", name_en=f"Module {module_id}", group=group, published=True)

    def _effect(self, module: ItemType, dogma_effect_id: int) -> TypeEffect:
        effect = DogmaEffect.objects.get_or_create(id=dogma_effect_id, defaults={"name": f"Effect {dogma_effect_id}"})[0]
        return TypeEffect.objects.create(item_type=module, dogma_effect=effect)

    def test_low_slot_from_dogma_effect(self) -> None:
        module = self._module(1, self.group_module)
        self._effect(module, 11)

        self.assertEqual(module_slot(module), SlotEnum.LOW)

    def test_medium_slot_from_dogma_effect(self) -> None:
        module = self._module(2, self.group_module)
        self._effect(module, 13)

        self.assertEqual(module_slot(module), SlotEnum.MEDIUM)

    def test_high_slot_from_dogma_effect(self) -> None:
        module = self._module(3, self.group_module)
        self._effect(module, 12)

        self.assertEqual(module_slot(module), SlotEnum.HIGH)

    def test_rig_slot_from_dogma_effect(self) -> None:
        module = self._module(4, self.group_module)
        self._effect(module, 2663)

        self.assertEqual(module_slot(module), SlotEnum.RIG)

    def test_other_slot_from_subsystem_dogma_effect(self) -> None:
        module = self._module(5, self.group_module)
        self._effect(module, 3772)

        self.assertEqual(module_slot(module), SlotEnum.OTHER)

    def test_cargo_slot_from_category_fallback(self) -> None:
        module = self._module(6, self.group_implant)

        self.assertEqual(module_slot(module), SlotEnum.CARGO)

    def test_drone_slot_from_category_fallback(self) -> None:
        module = self._module(7, self.group_drone)

        self.assertEqual(module_slot(module), SlotEnum.DRONE)

    def test_other_slot_when_no_effect_or_category_match(self) -> None:
        module = self._module(8, self.group_other)

        self.assertEqual(module_slot(module), SlotEnum.OTHER)

    def test_other_slot_when_group_is_none(self) -> None:
        module = self._module(9, None)

        self.assertEqual(module_slot(module), SlotEnum.OTHER)


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 post(self, path: str, **kwargs: object):
        def decorator(func):
            self.routes[path] = func
            return func
        return decorator


class ModulesAPITestBase(ModulesFixtureTestCase):
    def setUp(self) -> None:
        super().setUp()
        self.factory = RequestFactory()
        fake_api = _FakeAPI()
        modules_api.ModulesAPIEndpoints(fake_api)
        self.routes = fake_api.routes


class TestModuleInfo(ModulesAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes["/module/info"]

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

        response = self.view(request, modules_api.ModuleInfoRequest(ids=[1]))

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

    def test_returns_module_info_keyed_by_id(self) -> None:
        module = ItemType.objects.create(id=1, name="Named Module", name_en="Named Module", group=self.group_implant, published=True)
        request = self.factory.post("/module/info")
        request.user = _user(**{"incursions.basic_waitlist": True})

        response = self.view(request, modules_api.ModuleInfoRequest(ids=[module.id]))

        self.assertEqual(response[module.id].name, "Named Module")
        self.assertEqual(response[module.id].category, "Implant")
        self.assertEqual(response[module.id].slot, SlotEnum.CARGO)

    def test_handles_module_without_group(self) -> None:
        module = ItemType.objects.create(id=2, name="Groupless Module", name_en="Groupless Module", group=None, published=True)
        request = self.factory.post("/module/info")
        request.user = _user(**{"incursions.basic_waitlist": True})

        response = self.view(request, modules_api.ModuleInfoRequest(ids=[module.id]))

        self.assertEqual(response[module.id].category, "")
        self.assertEqual(response[module.id].slot, SlotEnum.OTHER)


class TestModulePreload(ModulesAPITestBase):
    def setUp(self) -> None:
        super().setUp()
        self.view = self.routes["/module/preload"]

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

        response = self.view(request)

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

    def test_extracts_type_ids_from_fitting_dna(self) -> None:
        module = ItemType.objects.create(id=1, name="Named Module", name_en="Named Module", group=self.group_implant, published=True)
        ship = ItemType.objects.create(id=100, name="Ship", name_en="Ship", group=self.group_module, published=True)
        ApprovedFitting.objects.create(ship=ship, name="Doctrine Fit", dna=f"{module.id};4:invalid;1::")
        request = self.factory.get("/module/preload")
        request.user = _user(**{"incursions.basic_waitlist": True})

        response = self.view(request)

        self.assertIn(module.id, response)
        self.assertEqual(response[module.id].name, "Named Module")
