# Standard Library
from types import SimpleNamespace
from unittest.mock import Mock, patch

# Django
from django.contrib import admin
from django.test import RequestFactory, SimpleTestCase

import incursions.admin as admin_module
from incursions import views
from incursions.tasks import LAST_MODIFIED_FORMAT, _parse_last_modified, update_all_fleets


class TestAdminFormFields(SimpleTestCase):
    def setUp(self) -> None:
        self.request = RequestFactory().get("/admin/")

    @patch("incursions.admin.Incursion.objects.exclude", return_value="incursions")
    @patch("django.contrib.admin.ModelAdmin.formfield_for_foreignkey", return_value="field")
    def test_focus_admin_filters_open_incursions(self, mock_super: Mock, mock_exclude: Mock) -> None:
        admin_obj = admin_module.FocusAdmin(admin_module.Focus, admin.site)

        result = admin_obj.formfield_for_foreignkey(SimpleNamespace(name="incursion"), self.request)

        self.assertEqual(result, "field")
        mock_exclude.assert_called_once_with(state=admin_module.Incursion.States.ENDED)
        self.assertEqual(mock_super.call_args.kwargs["queryset"], "incursions")

    @patch("incursions.admin.ItemType.objects.filter", return_value="ships")
    @patch("django.contrib.admin.ModelAdmin.formfield_for_foreignkey", return_value="field")
    def test_ship_field_admins_filter_ship_category(self, mock_super: Mock, mock_filter: Mock) -> None:
        ship_field = SimpleNamespace(name="ship")

        fitting_admin = admin_module.FittingAdmin(admin_module.Fitting, admin.site)
        fitting_admin.formfield_for_foreignkey(ship_field, self.request)

        rule_admin = admin_module.WaitlistCategoryRuleAdmin(admin_module.WaitlistCategoryRule, admin.site)
        rule_admin.formfield_for_foreignkey(ship_field, self.request)

        approved_admin = admin_module.ApprovedFittingAdmin(admin_module.ApprovedFitting, admin.site)
        approved_admin.formfield_for_foreignkey(ship_field, self.request)

        self.assertEqual(mock_filter.call_count, 3)
        for call in mock_filter.call_args_list:
            self.assertEqual(call.kwargs, {"group__category_id": admin_module.EVECATEGORY_SHIP})
        self.assertEqual(mock_super.call_count, 3)

    @patch("incursions.admin.ItemType.objects.filter", return_value="skills")
    @patch("django.contrib.admin.ModelAdmin.formfield_for_foreignkey", return_value="field")
    def test_skillcheck_admin_filters_skill_category(self, mock_super: Mock, mock_filter: Mock) -> None:
        admin_obj = admin_module.SkillCheckAdmin(admin_module.SkillCheck, admin.site)

        result = admin_obj.formfield_for_foreignkey(SimpleNamespace(name="skill"), self.request)

        self.assertEqual(result, "field")
        mock_filter.assert_called_once_with(group__category_id=admin_module.EVECATEGORY_SKILLS)
        self.assertEqual(mock_super.call_args.kwargs["queryset"], "skills")

    @patch("incursions.admin.ItemType.objects.filter", return_value="ships")
    @patch("django.contrib.admin.ModelAdmin.formfield_for_dbfield", return_value="field")
    def test_approved_skills_admin_filters_ship_category(self, mock_super: Mock, mock_filter: Mock) -> None:
        admin_obj = admin_module.ApprovedSkillsAdmin(admin_module.ApprovedSkills, admin.site)

        result = admin_obj.formfield_for_dbfield(SimpleNamespace(name="ship"), self.request)

        self.assertEqual(result, "field")
        mock_filter.assert_called_once_with(group__category_id=admin_module.EVECATEGORY_SHIP)
        self.assertEqual(mock_super.call_args.kwargs["queryset"], "ships")


class TestViews(SimpleTestCase):
    def setUp(self) -> None:
        self.request = RequestFactory().get("/incursions/waitlist/")

    @patch("incursions.views.render", return_value="response")
    def test_waitlist_view_renders_template_with_expected_context(self, mock_render: Mock) -> None:
        result = views.waitlist.__wrapped__(self.request)

        self.assertEqual(result, "response")
        mock_render.assert_called_once()
        self.assertEqual(mock_render.call_args.args[1], "incursions/base-bs5.html")
        self.assertEqual(mock_render.call_args.kwargs["context"]["app_name"], "incursions/waitlist")

    @patch("incursions.views.redirect", return_value="redirect-response")
    def test_add_char_fc_redirects_to_waitlist(self, mock_redirect: Mock) -> None:
        undecorated = views.add_char_fc.__wrapped__.__wrapped__

        result = undecorated(self.request, token="token")

        self.assertEqual(result, "redirect-response")
        mock_redirect.assert_called_once_with("incursions:waitlist")


class TestTasks(SimpleTestCase):
    def test_parse_last_modified_returns_now_for_missing_and_invalid_values(self) -> None:
        missing = _parse_last_modified(None)
        invalid = _parse_last_modified("not-a-date")

        self.assertIsNotNone(missing.tzinfo)
        self.assertIsNotNone(invalid.tzinfo)

    def test_parse_last_modified_parses_valid_header(self) -> None:
        parsed = _parse_last_modified("Mon, 18 May 2026 12:00:00 GMT")

        self.assertEqual(parsed.strftime(LAST_MODIFIED_FORMAT), "Mon, 18 May 2026 12:00:00 UTC")

    @patch("incursions.tasks.update_fleet")
    @patch("incursions.tasks.logger.warning")
    @patch("incursions.tasks.ActiveFleet.get_solo")
    def test_update_all_fleets_logs_warning_when_no_active_fleet(
        self,
        mock_get_solo: Mock,
        mock_warning: Mock,
        mock_update_fleet: Mock,
    ) -> None:
        mock_get_solo.return_value = SimpleNamespace(fleet=None)

        update_all_fleets()

        mock_warning.assert_called_once_with("ActiveFleet has no attached fleet to update")
        mock_update_fleet.assert_not_called()

    @patch("incursions.tasks.update_fleet")
    @patch("incursions.tasks.ActiveFleet.get_solo")
    def test_update_all_fleets_updates_when_active_fleet_exists(
        self,
        mock_get_solo: Mock,
        mock_update_fleet: Mock,
    ) -> None:
        mock_get_solo.return_value = SimpleNamespace(fleet=SimpleNamespace(pk=99))

        update_all_fleets()

        mock_update_fleet.assert_called_once_with(99)
