"""Price-refresh task tests (logic-level, no broker)."""

from decimal import Decimal
from unittest.mock import patch

from django.test import TestCase, override_settings

from shop.models import JITA_BUY_PCT, JITA_SELL_PCT, HullListing, Shop
from shop.tasks import _apply_prices_for_type, _enqueue_pct_refreshes
from shop.tests._corptools import make_asset, make_owner_character, make_ship_type


class ApplyPricesForTypeTests(TestCase):
    def setUp(self):
        self.user, self.char = make_owner_character()
        self.ship = make_ship_type()
        self.shop_a = Shop.objects.create(slug="a", name="A", created_by=self.user)
        self.shop_b = Shop.objects.create(slug="b", name="B", created_by=self.user)

    def _listing(self, shop, mode=JITA_SELL_PCT, pct="90", price=0):
        return HullListing.objects.create(
            shop=shop,
            eve_type=self.ship,
            price_mode=mode,
            price_pct=Decimal(pct),
            price=price,
        )

    @patch("shop.tasks.jita_prices")
    def test_updates_all_shops_of_type(self, mock_jp):
        mock_jp.return_value = {"buy": 100, "sell": 200}
        la = self._listing(self.shop_a)  # 90% of sell 200 -> 180
        lb = self._listing(self.shop_b)
        _apply_prices_for_type(self.ship.id)
        la.refresh_from_db()
        lb.refresh_from_db()
        self.assertEqual(la.price, 180)
        self.assertEqual(lb.price, 180)

    @patch("shop.tasks.jita_prices")
    def test_keep_last_known_when_side_none(self, mock_jp):
        mock_jp.return_value = {"buy": None, "sell": None}
        la = self._listing(self.shop_a, price=42)
        _apply_prices_for_type(self.ship.id)
        la.refresh_from_db()
        self.assertEqual(la.price, 42)


class EnqueueDedupTests(TestCase):
    def setUp(self):
        self.user, self.char = make_owner_character()
        self.ship = make_ship_type()
        self.ship2 = make_ship_type(type_id=672, name="Avatar")
        self.shop_a = Shop.objects.create(slug="a", name="A", created_by=self.user)
        self.shop_b = Shop.objects.create(slug="b", name="B", created_by=self.user)

    @patch("shop.tasks.refresh_price_for_type.apply_async")
    def test_one_task_per_distinct_type(self, mock_async):
        # same ship in two shops (pct) + a second ship (pct) + a fixed one (ignored)
        HullListing.objects.create(
            shop=self.shop_a,
            eve_type=self.ship,
            price_mode=JITA_BUY_PCT,
            price_pct=Decimal("95"),
        )
        HullListing.objects.create(
            shop=self.shop_b,
            eve_type=self.ship,
            price_mode=JITA_SELL_PCT,
            price_pct=Decimal("90"),
        )
        HullListing.objects.create(
            shop=self.shop_a,
            eve_type=self.ship2,
            price_mode=JITA_BUY_PCT,
            price_pct=Decimal("95"),
        )
        HullListing.objects.create(
            shop=self.shop_b, eve_type=self.ship2
        )  # fixed -> ignored
        _enqueue_pct_refreshes()
        enqueued = sorted(call.kwargs["args"][0] for call in mock_async.call_args_list)
        self.assertEqual(enqueued, [self.ship.id, self.ship2.id])


from shop.models import AUTO_STOCK, BPC, FIXED_STOCK, HULL, ShopSource  # noqa: E402
from shop.sections.base import handler_for  # noqa: E402
from shop.tasks import (  # noqa: E402
    _enqueue_stock_refreshes,
    _refresh_shop_stock,
    refresh_all_stock,
)


class RefreshShopStockTests(TestCase):
    def setUp(self):
        self.user, self.char = make_owner_character()
        self.ship = make_ship_type()
        self.station = 60003760
        self.shop = Shop.objects.create(slug="s", name="S", created_by=self.user)
        ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.char,
            location_id=self.station,
        )
        make_asset(
            self.char,
            type_id=self.ship.id,
            item_id=1,
            location_id=self.station,
            quantity=4,
        )

    def test_recomputes_auto_listing(self):
        listing = HullListing.objects.create(
            shop=self.shop, eve_type=self.ship, stock_mode=AUTO_STOCK, stock=0
        )
        _refresh_shop_stock(self.shop.id)
        listing.refresh_from_db()
        self.assertEqual(listing.stock, 4)

    def test_leaves_fixed_listing(self):
        listing = HullListing.objects.create(
            shop=self.shop, eve_type=self.ship, stock_mode=FIXED_STOCK, stock=7
        )
        _refresh_shop_stock(self.shop.id)
        listing.refresh_from_db()
        self.assertEqual(listing.stock, 7)


class EnqueueStockRefreshesTests(TestCase):
    def setUp(self):
        self.user, self.char = make_owner_character()
        self.ship = make_ship_type()

    @patch("shop.tasks.refresh_stock_for_shop.apply_async")
    def test_one_task_per_shop_with_auto_listing(self, mock_async):
        auto_shop = Shop.objects.create(slug="a", name="A", created_by=self.user)
        HullListing.objects.create(
            shop=auto_shop, eve_type=self.ship, stock_mode=AUTO_STOCK
        )
        fixed_shop = Shop.objects.create(slug="f", name="F", created_by=self.user)
        HullListing.objects.create(
            shop=fixed_shop, eve_type=self.ship, stock_mode=FIXED_STOCK
        )
        Shop.objects.create(slug="empty", name="E", created_by=self.user)  # no listings
        _enqueue_stock_refreshes()
        enqueued = sorted(call.kwargs["args"][0] for call in mock_async.call_args_list)
        self.assertEqual(enqueued, [auto_shop.id])


class HullSourceRefreshTests(TestCase):
    """HullSectionHandler.enqueue_source_refresh — force-pull corptools assets."""

    def setUp(self):
        self.user, self.char = make_owner_character()
        self.ship = make_ship_type()
        self.station = 60003760
        self.handler = handler_for(HULL)

    def _auto_shop(self, slug, character):
        shop = Shop.objects.create(slug=slug, name=slug, created_by=self.user)
        HullListing.objects.create(shop=shop, eve_type=self.ship, stock_mode=AUTO_STOCK)
        ShopSource.objects.create(
            shop=shop,
            section=HULL,
            character_audit=character,
            location_id=self.station,
        )
        return shop

    @patch("corptools.tasks.character.assets.update_char_assets.apply_async")
    def test_forces_update_for_auto_stock_characters(self, mock_async):
        self._auto_shop("a", self.char)
        self.handler.enqueue_source_refresh()
        self.assertEqual(mock_async.call_count, 1)
        self.assertEqual(
            mock_async.call_args.kwargs["args"],
            [self.char.character.character_id],
        )
        self.assertEqual(
            mock_async.call_args.kwargs["kwargs"],
            {"force_refresh": True},
        )

    @patch("corptools.tasks.character.assets.update_char_assets.apply_async")
    def test_dedupes_character_across_shops(self, mock_async):
        self._auto_shop("a", self.char)
        self._auto_shop("b", self.char)  # same character, two shops
        self.handler.enqueue_source_refresh()
        self.assertEqual(mock_async.call_count, 1)

    @patch("corptools.tasks.character.assets.update_char_assets.apply_async")
    def test_skips_fixed_only_shops(self, mock_async):
        shop = Shop.objects.create(slug="f", name="F", created_by=self.user)
        HullListing.objects.create(
            shop=shop, eve_type=self.ship, stock_mode=FIXED_STOCK
        )
        ShopSource.objects.create(
            shop=shop,
            section=HULL,
            character_audit=self.char,
            location_id=self.station,
        )
        self.handler.enqueue_source_refresh()
        mock_async.assert_not_called()

    @override_settings(SHOP_REFRESH_CORPTOOLS_ASSETS=False)
    @patch("corptools.tasks.character.assets.update_char_assets.apply_async")
    def test_disabled_setting_enqueues_nothing(self, mock_async):
        self._auto_shop("a", self.char)
        result = self.handler.enqueue_source_refresh()
        self.assertEqual(result, [])
        mock_async.assert_not_called()


class BpcSourceRefreshTests(TestCase):
    """BpcSectionHandler — blueprint mirror refresh + which shops to recompute."""

    def setUp(self):
        self.user, self.char = make_owner_character()
        self.station = 60003760
        self.shop = Shop.objects.create(slug="b", name="B", created_by=self.user)
        ShopSource.objects.create(
            shop=self.shop,
            section=BPC,
            character_audit=self.char,
            location_id=self.station,
        )
        self.handler = handler_for(BPC)

    @patch("shop.tasks.refresh_blueprints_for_character.apply_async")
    def test_enqueues_blueprint_refresh_per_character(self, mock_async):
        result = self.handler.enqueue_source_refresh()
        self.assertEqual(result, [self.char.pk])
        self.assertEqual(mock_async.call_args.kwargs["args"], [self.char.pk])

    def test_shops_to_recompute_are_bpc_sourced(self):
        self.assertEqual(list(self.handler.shops_to_recompute()), [self.shop.pk])


class RefreshAllStockTests(TestCase):
    """The single beat: refresh each section's sources, then enqueue recompute."""

    def setUp(self):
        self.user, self.char = make_owner_character()
        self.ship = make_ship_type()
        self.station = 60003760
        # hull shop (auto-stock) + bpc shop, each with a stockroom.
        self.hull_shop = Shop.objects.create(slug="h", name="H", created_by=self.user)
        HullListing.objects.create(
            shop=self.hull_shop, eve_type=self.ship, stock_mode=AUTO_STOCK
        )
        ShopSource.objects.create(
            shop=self.hull_shop,
            section=HULL,
            character_audit=self.char,
            location_id=self.station,
        )
        self.bpc_shop = Shop.objects.create(slug="b", name="B", created_by=self.user)
        ShopSource.objects.create(
            shop=self.bpc_shop,
            section=BPC,
            character_audit=self.char,
            location_id=self.station,
        )

    @patch("shop.tasks.refresh_stock_for_shop.apply_async")
    @patch("shop.tasks.refresh_blueprints_for_character.apply_async")
    @patch("corptools.tasks.character.assets.update_char_assets.apply_async")
    def test_refreshes_all_sections_and_enqueues_recompute(
        self, mock_assets, mock_bp, mock_stock
    ):
        refresh_all_stock()
        # hull sources -> corptools asset pull; bpc sources -> blueprint mirror.
        mock_assets.assert_called_once()
        mock_bp.assert_called_once()
        # both shops get a per-shop recompute enqueued.
        enqueued = sorted(c.kwargs["args"][0] for c in mock_stock.call_args_list)
        self.assertEqual(enqueued, sorted([self.hull_shop.id, self.bpc_shop.id]))


class RefreshCorpBlueprintsTaskTests(TestCase):
    def test_calls_refresh_for_the_corporation(self):
        from unittest.mock import patch

        from shop.tasks import refresh_blueprints_for_corporation
        from shop.tests._corptools import make_corp_owner

        _user, corp = make_corp_owner()
        with patch("shop.sections.bpc.blueprints.refresh_corporation_blueprints") as m:
            refresh_blueprints_for_corporation(corp.pk)
        m.assert_called_once()
        self.assertEqual(m.call_args.args[0].pk, corp.pk)

    def test_missing_corporation_is_noop(self):
        from unittest.mock import patch

        from shop.tasks import refresh_blueprints_for_corporation

        with patch("shop.sections.bpc.blueprints.refresh_corporation_blueprints") as m:
            refresh_blueprints_for_corporation(999999)
        m.assert_not_called()
