"""Stock computation tests."""

from unittest.mock import patch

from django.test import TestCase

from shop.models import AUTO_STOCK, FIXED_STOCK, HULL, HullListing, Shop, ShopSource
from shop.sections.hulls.stock import recompute_shop_stock
from shop.tests._corptools import (  # noqa: F401
    make_asset,
    make_container_type,
    make_owner_character,
    make_ship_sde_type,
    make_ship_type,
)


class StockTests(TestCase):
    def setUp(self):
        self.user, self.audit = make_owner_character()
        self.ship = make_ship_type()
        self.shop = Shop.objects.create(slug="s", name="S", created_by=self.user)
        self.listing = HullListing.objects.create(shop=self.shop, eve_type=self.ship)

    def _asset(self, item_id, qty=1, location_id=60003760, type_id=671):
        return make_asset(
            self.audit,
            type_id=type_id,
            item_id=item_id,
            location_id=location_id,
            quantity=qty,
        )

    def test_station_hangar_stock(self):
        self._asset(1, location_id=60003760)
        self._asset(2, location_id=60003760)
        ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.audit,
            location_id=60003760,
        )
        self.assertEqual(self.listing.recompute_stock(), 2)

    def test_container_stock(self):
        # Assets live inside a container: their location_id is the container item_id.
        self._asset(3, location_id=500)
        self._asset(4, location_id=500)
        ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.audit,
            location_id=60003760,
            container_item_id=500,
        )
        self.assertEqual(self.listing.recompute_stock(), 2)

    def test_multiple_sources_summed(self):
        self._asset(1, location_id=60003760)
        self._asset(2, location_id=60003761)
        ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.audit,
            location_id=60003760,
        )
        ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.audit,
            location_id=60003761,
        )
        self.assertEqual(self.listing.recompute_stock(), 2)

    def test_zero_when_no_assets(self):
        ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.audit,
            location_id=60003760,
        )
        self.assertEqual(self.listing.recompute_stock(), 0)


class PersistRemovalTests(TestCase):
    def setUp(self):
        self.user, self.audit = make_owner_character()
        self.ship = make_ship_type()
        self.shop = Shop.objects.create(slug="s", name="S", created_by=self.user)
        # A stockroom exists but holds no assets of this ship -> stock recomputes to 0.
        ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.audit,
            location_id=60003760,
        )

    def _listing(self, **kw):
        return HullListing.objects.create(shop=self.shop, eve_type=self.ship, **kw)

    def test_non_persist_auto_stock_at_zero_is_removed(self):
        listing = self._listing(stock_mode=AUTO_STOCK, persist=False)
        recompute_shop_stock(self.shop)
        self.assertFalse(HullListing.objects.filter(pk=listing.pk).exists())

    def test_persist_auto_stock_at_zero_is_kept(self):
        listing = self._listing(stock_mode=AUTO_STOCK, persist=True)
        recompute_shop_stock(self.shop)
        self.assertTrue(HullListing.objects.filter(pk=listing.pk).exists())

    def test_auto_stock_with_stock_is_kept(self):
        make_asset(self.audit, type_id=671, item_id=1, location_id=60003760, quantity=1)
        listing = self._listing(stock_mode=AUTO_STOCK, persist=False)
        recompute_shop_stock(self.shop)
        self.assertTrue(HullListing.objects.filter(pk=listing.pk).exists())

    def test_fixed_stock_at_zero_is_kept_even_when_not_persist(self):
        listing = self._listing(stock_mode=FIXED_STOCK, stock=0, persist=False)
        recompute_shop_stock(self.shop)
        self.assertTrue(HullListing.objects.filter(pk=listing.pk).exists())


class ShipTypeDetectionTests(TestCase):
    def setUp(self):
        self.user, self.audit = make_owner_character()
        self.shop = Shop.objects.create(slug="s2", name="S2", created_by=self.user)
        self.source = ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.audit,
            location_id=60003760,
        )

    def test_returns_only_ship_category_type_ids(self):
        from shop.sections.hulls.stock import ship_type_ids_in_source

        make_ship_sde_type(type_id=671, category_id=6)  # ship
        make_ship_sde_type(type_id=888, category_id=7, group_id=99)  # module (not ship)
        make_asset(self.audit, type_id=671, item_id=1, location_id=60003760)
        make_asset(self.audit, type_id=888, item_id=2, location_id=60003760)
        self.assertEqual(set(ship_type_ids_in_source(self.source)), {671})


class AutoListTests(TestCase):
    def setUp(self):
        self.user, self.audit = make_owner_character()
        self.shop = Shop.objects.create(
            slug="s3", name="S3", created_by=self.user, enabled_sections=["hulls"]
        )
        self.source = ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.audit,
            location_id=60003760,
        )

    def test_stockroom_added_auto_lists_ships(self):
        from shop.sections.hulls.handler import HullSectionHandler

        make_ship_sde_type(type_id=671, category_id=6)  # eve_sde: it's a ship
        make_ship_type(type_id=671)  # eveuniverse: stored type
        make_asset(self.audit, type_id=671, item_id=1, location_id=60003760, quantity=2)
        HullSectionHandler().stockroom_added(
            self.source, {"price_mode": "fixed", "price_pct": None}
        )
        self.assertTrue(self.shop.hull_listings.filter(eve_type_id=671).exists())

    def test_stockroom_added_skips_unresolvable_type(self):
        """An ESI failure for one type must not abort auto-listing: the bad type
        is skipped, a resolvable ship is still listed."""
        from shop.sections.hulls.handler import HullSectionHandler

        BAD = 999
        make_ship_sde_type(type_id=671, category_id=6)  # resolvable ship
        make_ship_sde_type(type_id=BAD, category_id=6)  # ship, but ESI will fail
        make_ship_type(type_id=671)  # eveuniverse stored type
        make_asset(self.audit, type_id=671, item_id=1, location_id=60003760, quantity=1)
        make_asset(self.audit, type_id=BAD, item_id=2, location_id=60003760, quantity=1)

        real = __import__(
            "eveuniverse.models", fromlist=["EveType"]
        ).EveType.objects.get_or_create_esi

        def fake_get_or_create_esi(*, id, **kw):
            if id == BAD:
                raise RuntimeError("ESI unresolvable")
            return real(id=id, **kw)

        with patch(
            "eveuniverse.models.EveType.objects.get_or_create_esi",
            side_effect=fake_get_or_create_esi,
        ):
            HullSectionHandler().stockroom_added(
                self.source, {"price_mode": "fixed", "price_pct": None}
            )

        self.assertTrue(self.shop.hull_listings.filter(eve_type_id=671).exists())
        self.assertFalse(self.shop.hull_listings.filter(eve_type_id=BAD).exists())


class ReconcileOnRecomputeTests(TestCase):
    def setUp(self):
        self.user, self.audit = make_owner_character()
        self.shop = Shop.objects.create(
            slug="rc", name="RC", created_by=self.user, enabled_sections=["hulls"]
        )
        self.source = ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.audit,
            location_id=60003760,
            price_mode="jita_buy_pct",
            price_pct=90,
        )

    def _ship_in_hangar(self, type_id=671, item_id=1, qty=1):
        make_ship_sde_type(type_id=type_id, category_id=6)
        make_ship_type(type_id=type_id)
        make_asset(
            self.audit,
            type_id=type_id,
            item_id=item_id,
            location_id=60003760,
            quantity=qty,
        )

    def test_recompute_autolists_new_ship_from_stockroom(self):
        from shop.sections.hulls.handler import HullSectionHandler

        self._ship_in_hangar(type_id=671, qty=2)
        self.assertFalse(self.shop.hull_listings.filter(eve_type_id=671).exists())
        HullSectionHandler().recompute(self.shop)
        listing = self.shop.hull_listings.get(eve_type_id=671)
        self.assertEqual(listing.stock, 2)

    def test_recompute_autolist_inherits_source_price_rule(self):
        from shop.sections.hulls.handler import HullSectionHandler

        self._ship_in_hangar(type_id=671)
        HullSectionHandler().recompute(self.shop)
        listing = self.shop.hull_listings.get(eve_type_id=671)
        self.assertEqual(listing.price_mode, "jita_buy_pct")
        self.assertEqual(listing.price_pct, 90)

    def test_recompute_does_not_overwrite_existing_listing(self):
        from shop.models import HullListing
        from shop.sections.hulls.handler import HullSectionHandler

        self._ship_in_hangar(type_id=671)
        existing = HullListing.objects.create(
            shop=self.shop, eve_type_id=671, price_mode="fixed", price=123
        )
        HullSectionHandler().recompute(self.shop)
        existing.refresh_from_db()
        self.assertEqual(existing.price_mode, "fixed")
        self.assertEqual(existing.price, 123)

    def test_shops_to_recompute_includes_shop_with_hull_source_and_no_listings(self):
        from shop.sections.hulls.handler import HullSectionHandler

        # self.shop has a hull source but zero listings
        self.assertIn(self.shop.pk, set(HullSectionHandler().shops_to_recompute()))
