# Standard Library
from unittest.mock import Mock, patch

# 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 incursions.api import fittings as fittings_api
from incursions.models.waitlist import ApprovedFitting, ApprovedImplantSet, WaitlistCategory, WaitlistCategoryRule


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()
    fittings_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 FittingsAPITestBase(NoSocketsTestCase):
    def setUp(self) -> None:
        self.factory = RequestFactory()
        self.routes = _routes()
        self.view = self.routes["/fittings"]
        category = ItemCategory.objects.create(id=6, name="Ship", name_en="Ship", published=True)
        group = ItemGroup.objects.create(id=600, name="Cruiser", name_en="Cruiser", category=category, published=True)
        self.ship = ItemType.objects.create(id=601, name="Sample Cruiser", name_en="Sample Cruiser", group=group, published=True)


class TestFittings(FittingsAPITestBase):
    def test_denies_without_permission(self) -> None:
        request = self.factory.get("/fittings")
        request.user = _user()

        response = self.view(request)

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

    @patch("incursions.api.fittings.logger.info")
    def test_creates_logi_category_when_missing(self, mock_info: Mock) -> None:
        request = self.factory.get("/fittings")
        request.user = _user(**{"incursions.basic_waitlist": True})

        self.view(request)

        self.assertTrue(WaitlistCategory.objects.filter(name="LOGI").exists())
        mock_info.assert_any_call("LOGI category was created automatically")

    def test_does_not_recreate_existing_logi_category(self) -> None:
        WaitlistCategory.objects.create(name="LOGI")
        request = self.factory.get("/fittings")
        request.user = _user(**{"incursions.basic_waitlist": True})

        self.view(request)

        self.assertEqual(WaitlistCategory.objects.filter(name="LOGI").count(), 1)

    def test_returns_logi_rules_for_configured_ships(self) -> None:
        logi_category = WaitlistCategory.objects.create(name="LOGI")
        WaitlistCategoryRule.objects.create(waitlist_category=logi_category, ship=self.ship)
        request = self.factory.get("/fittings")
        request.user = _user(**{"incursions.basic_waitlist": True})

        status_code, response = self.view(request)

        self.assertEqual(status_code, 200)
        self.assertEqual(response.logi_rules, [self.ship.id])

    def test_returns_fittings_with_resolved_implant_set(self) -> None:
        implants = ApprovedImplantSet.objects.create(name="Amulet Set", set=ApprovedImplantSet.set_choices.AMULET, implants="1,2,3")
        with_implants = ApprovedFitting.objects.create(ship=self.ship, name="With Implants", dna="601::", implants=implants)
        without_implants = ApprovedFitting.objects.create(ship=self.ship, name="No Implants", dna="601:1:")
        request = self.factory.get("/fittings")
        request.user = _user(**{"incursions.basic_waitlist": True})

        status_code, response = self.view(request)

        self.assertEqual(status_code, 200)
        fittings_by_name = {f.name: f for f in response.fittingdata}
        self.assertEqual(fittings_by_name["With Implants"].implant_set, "Amulet")
        self.assertEqual(fittings_by_name["No Implants"].implant_set, "None")
        self.assertEqual({f.name for f in response.notes}, {"With Implants", "No Implants"})
        self.assertEqual(with_implants.pk, ApprovedFitting.objects.get(name="With Implants").pk)
        self.assertEqual(without_implants.implant_set(), "None")
