# Standard Library
from datetime import datetime, timedelta, timezone
from unittest.mock import Mock

# Django
from django.test import RequestFactory
from django.utils.timezone import now

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

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

from incursions.api import statistics
from incursions.models.waitlist import (
    Fitting, Fleet, FleetActivity, ImplantSet, Waitlist, WaitlistCategory, WaitlistEntry, WaitlistEntryFit,
)


class StatisticsFixtureTestCase(NoSocketsTestCase):
    def setUp(self) -> None:
        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_a = ItemType.objects.create(id=601, name="Ship A", name_en="Ship A", group=group, published=True)
        self.ship_b = ItemType.objects.create(id=602, name="Ship B", name_en="Ship B", group=group, published=True)

        self.char1 = EveCharacter.objects.create(character_id=1001, character_name="Pilot One")
        self.char2 = EveCharacter.objects.create(character_id=1002, character_name="Pilot Two")

        boss = EveCharacter.objects.create(character_id=9000, character_name="Boss")
        self.fleet = Fleet.objects.create(boss=boss, is_updating=True, open=True, last_updated=now())

        self.waitlist = Waitlist.get_solo()
        self.waitlist_category = WaitlistCategory.objects.create(name="SPONGE")

    def _fleet_activity(self, character: EveCharacter, ship: ItemType, first_seen: datetime, last_seen: datetime) -> FleetActivity:
        activity = FleetActivity.objects.create(fleet=self.fleet, character=character, ship=ship, last_seen=last_seen)
        FleetActivity.objects.filter(pk=activity.pk).update(first_seen=first_seen)
        return FleetActivity.objects.get(pk=activity.pk)

    def _waitlist_entry_fit(self, character: EveCharacter, ship: ItemType, joined_at: datetime) -> WaitlistEntryFit:
        entry = WaitlistEntry.objects.create(waitlist=self.waitlist, main_character=character)
        WaitlistEntry.objects.filter(pk=entry.pk).update(joined_at=joined_at)
        fitting = Fitting.objects.create(ship=ship, dna=f"{ship.pk}::")
        implants = ImplantSet.objects.create(implants=f"[{character.character_id}]")
        return WaitlistEntryFit.objects.create(
            character=character, waitlist_entry=WaitlistEntry.objects.get(pk=entry.pk), fit=fitting,
            implant_set=implants, category=self.waitlist_category, tags="",
        )


class TestGetFleetSecondsByShipByMonth(StatisticsFixtureTestCase):
    def test_groups_by_month_and_ship(self) -> None:
        jan = datetime(2026, 1, 15, 12, tzinfo=timezone.utc)
        feb = datetime(2026, 2, 15, 12, tzinfo=timezone.utc)
        self._fleet_activity(self.char1, self.ship_a, jan, jan + timedelta(hours=1))
        self._fleet_activity(self.char2, self.ship_a, feb, feb + timedelta(hours=2))

        result = statistics.get_fleet_seconds_by_ship_by_month()

        self.assertEqual(result["2026-01"]["Ship A"], 3600.0)
        self.assertEqual(result["2026-02"]["Ship A"], 7200.0)


class TestGetXesByShipByMonth(StatisticsFixtureTestCase):
    def test_groups_by_month_and_ship(self) -> None:
        jan = datetime(2026, 1, 10, tzinfo=timezone.utc)
        self._waitlist_entry_fit(self.char1, self.ship_a, jan)
        self._waitlist_entry_fit(self.char2, self.ship_b, jan)

        result = statistics.get_xes_by_ship_by_month()

        self.assertEqual(result["2026-01"]["Ship A"], 1)
        self.assertEqual(result["2026-01"]["Ship B"], 1)


class TestGetFleetSecondsByMonth(StatisticsFixtureTestCase):
    def test_sums_across_ships(self) -> None:
        jan = datetime(2026, 1, 15, tzinfo=timezone.utc)
        self._fleet_activity(self.char1, self.ship_a, jan, jan + timedelta(hours=1))
        self._fleet_activity(self.char2, self.ship_b, jan, jan + timedelta(hours=1))

        result = statistics.get_fleet_seconds_by_month()

        self.assertEqual(result["2026-01"], 7200.0)


class TestGetPilotsByMonth(StatisticsFixtureTestCase):
    def test_counts_distinct_pilots(self) -> None:
        jan = datetime(2026, 1, 15, tzinfo=timezone.utc)
        self._fleet_activity(self.char1, self.ship_a, jan, jan + timedelta(hours=1))
        self._fleet_activity(self.char2, self.ship_a, jan, jan + timedelta(hours=1))
        self._fleet_activity(self.char1, self.ship_b, jan, jan + timedelta(hours=1))

        result = statistics.get_pilots_by_month()

        self.assertEqual(result["2026-01"], 2)


class TestGetXesByShip28d(StatisticsFixtureTestCase):
    def test_excludes_entries_older_than_28_days(self) -> None:
        recent = now() - timedelta(days=5)
        old = now() - timedelta(days=40)
        self._waitlist_entry_fit(self.char1, self.ship_a, recent)
        self._waitlist_entry_fit(self.char2, self.ship_b, old)

        result = statistics.get_xes_by_ship_28d()

        self.assertEqual(result["Ship A"], 1)
        self.assertNotIn("Ship B", result)


class TestGetFleetSecondsByShip28d(StatisticsFixtureTestCase):
    def test_excludes_entries_older_than_28_days(self) -> None:
        recent = now() - timedelta(days=5)
        old = now() - timedelta(days=40)
        self._fleet_activity(self.char1, self.ship_a, recent, recent + timedelta(hours=1))
        self._fleet_activity(self.char2, self.ship_b, old, old + timedelta(hours=1))

        result = statistics.get_fleet_seconds_by_ship_28d()

        self.assertEqual(result["Ship A"], 3600.0)
        self.assertNotIn("Ship B", result)


class TestGetXVsTimeByShip28d(StatisticsFixtureTestCase):
    def test_combines_xes_and_seconds_per_ship(self) -> None:
        recent = now() - timedelta(days=5)
        self._waitlist_entry_fit(self.char1, self.ship_a, recent)
        self._fleet_activity(self.char1, self.ship_a, recent, recent + timedelta(hours=1))

        result = statistics.get_x_vs_time_by_ship_28d()

        self.assertEqual(result["Ship A"]["xes"], 1)
        self.assertEqual(result["Ship A"]["seconds"], 3600.0)

    def test_defaults_seconds_to_zero_when_no_fleet_activity(self) -> None:
        recent = now() - timedelta(days=5)
        self._waitlist_entry_fit(self.char1, self.ship_a, recent)

        result = statistics.get_x_vs_time_by_ship_28d()

        self.assertEqual(result["Ship A"]["xes"], 1)
        self.assertEqual(result["Ship A"]["seconds"], 0)


class TestGetTimeSpentInFleetByMonth(StatisticsFixtureTestCase):
    def test_groups_by_month_and_character(self) -> None:
        jan = datetime(2026, 1, 15, tzinfo=timezone.utc)
        self._fleet_activity(self.char1, self.ship_a, jan, jan + timedelta(hours=1))

        result = statistics.get_time_spent_in_fleet_by_month()

        self.assertEqual(result["2026-01"]["Pilot One"], 3600.0)


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


class TestStatisticsEndpoint(StatisticsFixtureTestCase):
    def setUp(self) -> None:
        super().setUp()
        self.factory = RequestFactory()
        fake_api = _FakeAPI()
        statistics.StatisticsAPIEndpoints(fake_api)
        self.view = fake_api.routes["/stats"]

    def test_denies_without_permission(self) -> None:
        request = self.factory.get("/stats")
        request.user = Mock(has_perm=Mock(return_value=False))

        response = self.view(request)

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

    def test_returns_stats_response_when_permitted(self) -> None:
        jan = datetime(2026, 1, 15, tzinfo=timezone.utc)
        self._fleet_activity(self.char1, self.ship_a, jan, jan + timedelta(hours=1))
        request = self.factory.get("/stats")
        request.user = Mock(has_perm=Mock(return_value=True))

        response = self.view(request)

        self.assertIsInstance(response, statistics.StatsResponse)
        self.assertEqual(response.fleet_seconds_by_month["2026-01"], 3600.0)
