from django.test import TestCase

from shop.models import ModuleListing, Shop
from shop.tests._corptools import make_owner_character, make_ship_type


class ModuleListingModelTests(TestCase):
    def setUp(self):
        self.user, _ = make_owner_character()
        self.shop = Shop.objects.create(slug="s", name="S", created_by=self.user)
        self.et = make_ship_type(type_id=2048, name="Damage Control II")

    def test_defaults_and_uniqueness(self):
        from django.db import IntegrityError, transaction

        m = ModuleListing.objects.create(shop=self.shop, eve_type=self.et)
        self.assertEqual(m.stock, 0)
        self.assertEqual(m.price, 0)
        self.assertEqual(m.price_display, "Ask")
        with self.assertRaises(IntegrityError):
            with transaction.atomic():
                ModuleListing.objects.create(shop=self.shop, eve_type=self.et)


from shop.models import MODULES, ShopSource  # noqa: E402
from shop.sections.modules.stock import (  # noqa: E402
    module_type_ids_in_source,
    recompute_module_stock,
)
from shop.tests._corptools import make_asset, make_ship_sde_type  # noqa: E402

STATION = 60003760


class ModuleStockTests(TestCase):
    def setUp(self):
        self.user, self.audit = make_owner_character()
        self.shop = Shop.objects.create(slug="ms", name="MS", created_by=self.user)
        self.src = ShopSource.objects.create(
            shop=self.shop,
            section=MODULES,
            character_audit=self.audit,
            location_id=STATION,
        )

    def test_only_allowlisted_categories_returned(self):
        make_ship_sde_type(type_id=2048, category_id=7, group_id=60)  # module -> yes
        make_ship_sde_type(type_id=2456, category_id=18, group_id=100)  # drone  -> yes
        make_ship_sde_type(
            type_id=20125, category_id=20, group_id=300
        )  # implant -> yes
        make_ship_sde_type(type_id=671, category_id=6, group_id=25)  # ship   -> no
        make_ship_sde_type(type_id=938, category_id=9, group_id=105)  # blueprint -> no
        for tid in (2048, 2456, 20125, 671, 938):
            make_asset(self.audit, type_id=tid, item_id=tid, location_id=STATION)
        self.assertEqual(set(module_type_ids_in_source(self.src)), {2048, 2456, 20125})

    def test_recompute_lists_in_stock_and_drops_at_zero(self):
        et = make_ship_type(type_id=2048, name="Damage Control II")
        make_ship_sde_type(type_id=2048, category_id=7)
        a = make_asset(
            self.audit, type_id=2048, item_id=1, location_id=STATION, quantity=3
        )
        ModuleListing.objects.create(shop=self.shop, eve_type=et)
        recompute_module_stock(self.shop)
        self.assertEqual(ModuleListing.objects.get(eve_type=et).stock, 3)
        a.delete()
        recompute_module_stock(self.shop)
        self.assertFalse(ModuleListing.objects.filter(eve_type=et).exists())


from shop.sections.modules.forms import EditModuleForm  # noqa: E402


class EditModuleFormTests(TestCase):
    def test_fixed_requires_price(self):
        self.assertFalse(EditModuleForm({"price_mode": "fixed"}).is_valid())
        self.assertTrue(EditModuleForm({"price_mode": "fixed", "price": 0}).is_valid())

    def test_pct_requires_pct(self):
        self.assertFalse(EditModuleForm({"price_mode": "jita_buy_pct"}).is_valid())
        self.assertTrue(
            EditModuleForm({"price_mode": "jita_buy_pct", "price_pct": "90"}).is_valid()
        )


from shop.sections import handler_for  # noqa: E402


class ModuleHandlerTests(TestCase):
    def setUp(self):
        self.user, self.audit = make_owner_character()
        self.shop = Shop.objects.create(slug="mh", name="MH", created_by=self.user)
        make_ship_type(type_id=2048, name="Damage Control II")
        make_ship_sde_type(type_id=2048, category_id=7, group_id=60)
        make_asset(self.audit, type_id=2048, item_id=1, location_id=STATION, quantity=5)
        self.src = ShopSource.objects.create(
            shop=self.shop,
            section=MODULES,
            character_audit=self.audit,
            location_id=STATION,
        )

    def test_registered(self):
        self.assertIsNotNone(handler_for(MODULES))

    def test_stockroom_added_autolists_with_stock(self):
        handler_for(MODULES).stockroom_added(
            self.src, {"price_mode": "jita_buy_pct", "price_pct": "90"}
        )
        listing = ModuleListing.objects.get(shop=self.shop, eve_type_id=2048)
        self.assertEqual(listing.stock, 5)
        self.assertEqual(listing.price_mode, "jita_buy_pct")

    def test_build_order_line(self):
        ModuleListing.objects.create(shop=self.shop, eve_type_id=2048, price=100)
        line = handler_for(MODULES).build_order_line(
            self.shop, ModuleListing.objects.get(eve_type_id=2048).pk
        )
        self.assertEqual(line["section"], MODULES)
        self.assertEqual(line["unit_price"], 100)


from allianceauth.tests.auth_utils import AuthUtils  # noqa: E402
from django.urls import reverse  # noqa: E402

from shop.tests.test_views_backoffice import no_manifest_static  # noqa: E402


@no_manifest_static
class ModuleViewTests(TestCase):
    def setUp(self):
        self.user, self.audit = make_owner_character(username="mv", char_id=1301)
        AuthUtils.add_permission_to_user_by_name("shop.basic_access", self.user)
        from django.contrib.auth.models import User

        self.user = User.objects.get(pk=self.user.pk)
        self.shop = Shop.objects.create(
            slug="mvs", name="MVS", created_by=self.user, enabled_sections=[MODULES]
        )
        self.et = make_ship_type(type_id=2048, name="Damage Control II")
        self.listing = ModuleListing.objects.create(
            shop=self.shop, eve_type=self.et, price=100, price_mode="fixed"
        )
        self.client.force_login(self.user)

    def test_edit_module_sets_fixed_price(self):
        resp = self.client.post(
            reverse("backoffice:edit_module", args=[self.shop.pk, self.listing.pk]),
            {"price_mode": "fixed", "price": 5000000},
            SERVER_NAME="localhost",
        )
        self.assertRedirects(
            resp,
            reverse("backoffice:manage_section", args=[self.shop.pk, "modules"]),
            fetch_redirect_response=False,
        )
        self.listing.refresh_from_db()
        self.assertEqual(self.listing.price, 5000000)

    def test_remove_module(self):
        self.client.post(
            reverse("backoffice:remove_module", args=[self.shop.pk, self.listing.pk]),
            SERVER_NAME="localhost",
        )
        self.assertFalse(ModuleListing.objects.filter(pk=self.listing.pk).exists())

    def test_manage_page_renders(self):
        self.shop.enabled_sections = [MODULES]
        self.shop.save()
        resp = self.client.get(
            reverse("backoffice:manage_section", args=[self.shop.pk, "modules"]),
            SERVER_NAME="localhost",
        )
        self.assertEqual(resp.status_code, 200)
        self.assertContains(resp, "Damage Control II")


@no_manifest_static
class ModuleStorefrontTests(TestCase):
    def setUp(self):
        self.user, self.audit = make_owner_character(username="sf", char_id=1401)
        self.shop = Shop.objects.create(
            slug="sfs", name="SFS", created_by=self.user, is_published=True
        )
        self.shop.enabled_sections = [MODULES]
        self.shop.save()
        et = make_ship_type(type_id=2048, name="Damage Control II")
        ModuleListing.objects.create(shop=self.shop, eve_type=et, price=100, stock=3)

    def test_storefront_modules_page_renders(self):
        for view in ("grid", "table"):
            resp = self.client.get(
                reverse("storefront:section", args=[self.shop.slug, "modules"]),
                {"view": view},
                SERVER_NAME="localhost",
            )
            self.assertEqual(resp.status_code, 200)
            self.assertContains(resp, "Damage Control II")

    def test_item_detail_renders_as_module_with_stock(self):
        listing = ModuleListing.objects.get(shop=self.shop)  # stock == 3
        resp = self.client.get(
            reverse("storefront:module_item", args=[self.shop.slug, listing.pk]),
            SERVER_NAME="localhost",
        )
        self.assertEqual(resp.status_code, 200)
        self.assertContains(resp, "Damage Control II")
        # the cart control must be a MODULE line, stock-capped — not a hull line
        self.assertContains(resp, 'data-section="modules"')
        self.assertContains(resp, 'data-stock="3"')
        self.assertNotContains(resp, 'data-section="hulls"')

    def _submit(self, lines):
        import json

        session = self.client.session
        session["shop_buyer"] = {"id": 90000001, "name": "Jane Pilot"}
        session.save()
        return self.client.post(
            reverse("storefront:submit_order", args=[self.shop.slug]),
            data=json.dumps({"lines": lines}),
            content_type="application/json",
            SERVER_NAME="localhost",
        )

    def test_order_quantity_clamped_to_stock(self):
        from shop.models import OrderLine

        listing = ModuleListing.objects.get(shop=self.shop)  # stock == 3
        resp = self._submit(
            [{"section": "modules", "listing_id": listing.pk, "qty": 10}]
        )
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(OrderLine.objects.get().quantity, 3)  # clamped from 10

    def test_duplicate_lines_cannot_exceed_stock(self):
        from django.db.models import Sum

        from shop.models import OrderLine

        listing = ModuleListing.objects.get(shop=self.shop)  # stock == 3
        resp = self._submit(
            [
                {"section": "modules", "listing_id": listing.pk, "qty": 2},
                {"section": "modules", "listing_id": listing.pk, "qty": 2},
            ]
        )
        self.assertEqual(resp.status_code, 200)
        total = OrderLine.objects.aggregate(n=Sum("quantity"))["n"]
        self.assertEqual(total, 3)  # 2 + clamped-1, not 4


class ModulePctPriceTests(TestCase):
    def test_pct_price_populates_for_module(self):
        from unittest.mock import patch

        from shop.tasks import _apply_prices_for_type, pct_type_ids

        user, _ = make_owner_character(username="pp", char_id=1501)
        shop = Shop.objects.create(slug="pp", name="PP", created_by=user)
        et = make_ship_type(type_id=2048, name="Damage Control II")
        ModuleListing.objects.create(
            shop=shop, eve_type=et, price_mode="jita_buy_pct", price_pct=100, price=0
        )
        self.assertIn(2048, pct_type_ids())
        with patch(
            "shop.tasks.jita_prices",
            return_value={"buy": 5000000, "sell": 6000000},
        ):
            _apply_prices_for_type(2048)
        self.assertEqual(ModuleListing.objects.get(eve_type=et).price, 5000000)


class ModuleCorpSourceTests(TestCase):
    def test_corp_stockroom_autolists_modules(self):
        from shop.tests._corptools import (
            make_corp_asset,
            make_corp_division,
            make_corp_owner,
        )

        user, corp = make_corp_owner()
        shop = Shop.objects.create(
            slug="mc",
            name="MC",
            created_by=user,
            shop_type=Shop.ShopType.CORPORATION,
        )
        div = -600037601
        make_corp_division(corp, location_id=div)
        make_ship_type(type_id=2049, name="Large Shield Extender II")
        make_ship_sde_type(type_id=2049, category_id=7, group_id=61)
        make_corp_asset(
            corp,
            type_id=2049,
            item_id=1,
            location_id=700000001,
            division_location_id=div,
            quantity=7,
        )
        src = ShopSource.objects.create(
            shop=shop, section=MODULES, corporation_audit=corp, location_id=div
        )
        handler_for(MODULES).stockroom_added(
            src, {"price_mode": "fixed", "price_pct": None}
        )
        self.assertEqual(
            ModuleListing.objects.get(shop=shop, eve_type_id=2049).stock, 7
        )
