"""Manage page tests (hull stockrooms + listings)."""

from allianceauth.tests.auth_utils import AuthUtils
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse

from shop.models import HULL, HullListing, Shop, ShopSource
from shop.tests._corptools import (
    make_asset,
    make_container_type,
    make_owner_character,
    make_ship_sde_type,
    make_ship_type,
)
from shop.tests.test_views_backoffice import no_manifest_static


def member_with_access(username="mgr", char_id=1001):
    user, char = make_owner_character(username, char_id)
    AuthUtils.add_permission_to_user_by_name("shop.basic_access", user)
    return User.objects.get(pk=user.pk), char


@no_manifest_static
class ManagePageTests(TestCase):
    def setUp(self):
        self.user, self.char = member_with_access()
        self.shop = Shop.objects.create(
            slug="s", name="S", created_by=self.user, enabled_sections=[HULL]
        )
        make_ship_sde_type(type_id=671, category_id=6)
        self.ship = make_ship_type()
        self.station = 60003760
        self.client.force_login(self.user)

    def _asset(self, item_id, qty=1):
        return make_asset(
            self.char,
            type_id=self.ship.id,
            item_id=item_id,
            location_id=self.station,
            quantity=qty,
        )

    # --- stockrooms -------------------------------------------------------

    def test_manage_requires_can_manage(self):
        other, _ = member_with_access("intruder", char_id=2002)
        self.client.force_login(other)
        resp = self.client.get(
            reverse("backoffice:manage", args=[self.shop.pk]), SERVER_NAME="localhost"
        )
        self.assertEqual(resp.status_code, 403)

    def test_manage_renders_existing_stockroom_row(self):
        # Regression: _stockrooms.html must read the corptools character_audit +
        # location_display, not the removed memberaudit character/location FKs.
        ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.char,
            location_id=self.station,
        )
        resp = self.client.get(
            reverse("backoffice:manage", args=[self.shop.pk]),
            SERVER_NAME="localhost",
            follow=True,  # manage redirects to the first section's manage page
        )
        self.assertEqual(resp.status_code, 200)
        self.assertContains(resp, self.char.character.character_name)
        self.assertContains(resp, str(self.station))  # location_display id fallback

    def test_add_stockroom_autolists_hulls_with_stock(self):
        # an Erebus sits in the station hangar; no listing exists yet
        self._asset(10)
        # a module in the same hangar must NOT become a hull listing
        from eveuniverse.models import EveCategory, EveGroup, EveType

        cat = EveCategory.objects.create(id=7, name="Module", published=True)
        grp = EveGroup.objects.create(
            id=60, name="Afterburner", eve_category=cat, published=True
        )
        module = EveType.objects.create(
            id=12056, name="1MN Afterburner", eve_group=grp, published=True
        )
        make_asset(
            self.char,
            type_id=module.id,
            item_id=99,
            location_id=self.station,
            quantity=3,
        )
        resp = self.client.post(
            reverse("backoffice:add_stockroom", args=[self.shop.pk, "hulls"]),
            {
                "owner_id": self.char.id,
                "location_id": self.station,
                "container_item_id": "",
                "price_mode": "fixed",
            },
            SERVER_NAME="localhost",
        )
        self.assertRedirects(
            resp, reverse("backoffice:manage_section", args=[self.shop.pk, "hulls"])
        )
        self.assertEqual(self.shop.sources.filter(section=HULL).count(), 1)
        # exactly one hull listing (Erebus) with computed stock; module ignored
        self.assertEqual(HullListing.objects.filter(shop=self.shop).count(), 1)
        listing = HullListing.objects.get(shop=self.shop, eve_type=self.ship)
        self.assertEqual(listing.stock, 1)

    def test_remove_stockroom_recomputes(self):
        # persist=True so the listing survives the drop to 0 and we can assert on stock.
        listing = HullListing.objects.create(
            shop=self.shop, eve_type=self.ship, stock=5, persist=True
        )
        src = ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.char,
            location_id=self.station,
        )
        resp = self.client.post(
            reverse("backoffice:remove_stockroom", args=[self.shop.pk, src.pk]),
            SERVER_NAME="localhost",
        )
        self.assertRedirects(
            resp, reverse("backoffice:manage_section", args=[self.shop.pk, "hulls"])
        )
        self.assertFalse(ShopSource.objects.filter(pk=src.pk).exists())
        listing.refresh_from_db()
        self.assertEqual(listing.stock, 0)

    def test_remove_stockroom_auto_removes_non_persist_listing(self):
        listing = HullListing.objects.create(
            shop=self.shop, eve_type=self.ship, stock=5
        )
        src = ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.char,
            location_id=self.station,
        )
        self.client.post(
            reverse("backoffice:remove_stockroom", args=[self.shop.pk, src.pk]),
            SERVER_NAME="localhost",
        )
        self.assertFalse(HullListing.objects.filter(pk=listing.pk).exists())

    def test_add_stockroom_rejects_foreign_character(self):
        self.client.post(
            reverse("backoffice:add_stockroom", args=[self.shop.pk, "hulls"]),
            {
                "owner_id": 999999,
                "location_id": self.station,
                "container_item_id": "",
                "price_mode": "fixed",
            },
            SERVER_NAME="localhost",
        )
        self.assertEqual(self.shop.sources.count(), 0)

    # --- listings ---------------------------------------------------------

    def test_add_hull_creates_listing_with_stock(self):
        ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.char,
            location_id=self.station,
        )
        self._asset(11)
        resp = self.client.post(
            reverse("backoffice:add_hull", args=[self.shop.pk]),
            {
                "eve_type": self.ship.id,
                "stock_mode": "auto",
                "price_mode": "fixed",
                "price": "0",
            },
            SERVER_NAME="localhost",
        )
        self.assertRedirects(
            resp, reverse("backoffice:manage_section", args=[self.shop.pk, "hulls"])
        )
        listing = HullListing.objects.get(shop=self.shop, eve_type=self.ship)
        self.assertEqual(listing.stock, 1)

    def test_add_hull_rejects_non_ship(self):
        from eveuniverse.models import EveCategory, EveGroup, EveType

        cat = EveCategory.objects.create(id=7, name="Module", published=True)
        grp = EveGroup.objects.create(
            id=60, name="Afterburner", eve_category=cat, published=True
        )
        module = EveType.objects.create(
            id=12056, name="1MN Afterburner", eve_group=grp, published=True
        )
        self.client.post(
            reverse("backoffice:add_hull", args=[self.shop.pk]),
            {
                "eve_type": module.id,
                "stock_mode": "auto",
                "price_mode": "fixed",
                "price": "0",
            },
            SERVER_NAME="localhost",
        )
        self.assertFalse(
            HullListing.objects.filter(shop=self.shop, eve_type=module).exists()
        )

    def test_remove_hull(self):
        listing = HullListing.objects.create(shop=self.shop, eve_type=self.ship)
        resp = self.client.post(
            reverse("backoffice:remove_hull", args=[self.shop.pk, listing.pk]),
            SERVER_NAME="localhost",
        )
        self.assertRedirects(
            resp, reverse("backoffice:manage_section", args=[self.shop.pk, "hulls"])
        )
        self.assertFalse(HullListing.objects.filter(pk=listing.pk).exists())


from decimal import Decimal  # noqa: E402
from unittest.mock import patch  # noqa: E402

from shop.models import (  # noqa: E402
    AUTO_STOCK,
    FIXED_PRICE,
    FIXED_STOCK,
    JITA_SELL_PCT,
)


@no_manifest_static
class EditHullViewTests(TestCase):
    def setUp(self):
        self.user, self.char = member_with_access()
        self.shop = Shop.objects.create(
            slug="s", name="S", created_by=self.user, enabled_sections=[HULL]
        )
        self.ship = make_ship_type()
        self.listing = HullListing.objects.create(shop=self.shop, eve_type=self.ship)
        self.client.force_login(self.user)

    def _url(self):
        return reverse("backoffice:edit_hull", args=[self.shop.pk, self.listing.pk])

    def test_switch_to_fixed_stock(self):
        self.client.post(
            self._url(),
            {
                "stock_mode": FIXED_STOCK,
                "stock": "15",
                "price_mode": FIXED_PRICE,
                "price": "0",
            },
            SERVER_NAME="localhost",
        )
        self.listing.refresh_from_db()
        self.assertEqual(self.listing.stock_mode, FIXED_STOCK)
        self.assertEqual(self.listing.stock, 15)

    @patch("shop.views.backoffice.jita_prices")
    def test_switch_to_pct_computes_now(self, mock_jp):
        mock_jp.return_value = {"buy": 100, "sell": 200}
        self.client.post(
            self._url(),
            {
                "stock_mode": AUTO_STOCK,
                "price_mode": JITA_SELL_PCT,
                "price_pct": "50",
            },
            SERVER_NAME="localhost",
        )
        self.listing.refresh_from_db()
        self.assertEqual(self.listing.price_mode, JITA_SELL_PCT)
        self.assertEqual(self.listing.price_pct, Decimal("50.00"))
        self.assertEqual(self.listing.price, 100)  # 50% of sell 200

    def test_requires_can_manage(self):
        other, _ = member_with_access("intruder", char_id=2002)
        self.client.force_login(other)
        resp = self.client.post(
            self._url(),
            {
                "stock_mode": AUTO_STOCK,
                "price_mode": FIXED_PRICE,
                "price": "0",
            },
            SERVER_NAME="localhost",
        )
        self.assertEqual(resp.status_code, 403)

    def test_sets_persist(self):
        self.client.post(
            self._url(),
            {
                "stock_mode": FIXED_STOCK,
                "stock": "5",
                "price_mode": FIXED_PRICE,
                "price": "0",
                "persist": "on",
            },
            SERVER_NAME="localhost",
        )
        self.listing.refresh_from_db()
        self.assertTrue(self.listing.persist)

    def test_persist_unchecked_clears(self):
        self.listing.persist = True
        self.listing.save(update_fields=["persist"])
        self.client.post(
            self._url(),
            {
                "stock_mode": FIXED_STOCK,
                "stock": "5",
                "price_mode": FIXED_PRICE,
                "price": "0",
            },
            SERVER_NAME="localhost",
        )
        self.listing.refresh_from_db()
        self.assertFalse(self.listing.persist)

    def test_saves_description(self):
        self.client.post(
            self._url(),
            {
                "stock_mode": "auto",
                "price_mode": "fixed",
                "price": "0",
                "description": "**buy me**",
            },
            SERVER_NAME="localhost",
        )
        self.listing.refresh_from_db()
        self.assertEqual(self.listing.description, "**buy me**")


@no_manifest_static
class AddHullModesTests(TestCase):
    def setUp(self):
        self.user, self.char = member_with_access()
        self.shop = Shop.objects.create(
            slug="s", name="S", created_by=self.user, enabled_sections=[HULL]
        )
        self.ship = make_ship_type()
        self.client.force_login(self.user)

    def _url(self):
        return reverse("backoffice:add_hull", args=[self.shop.pk])

    def test_add_hull_fixed_price(self):
        self.client.post(
            self._url(),
            {
                "eve_type": self.ship.id,
                "stock_mode": AUTO_STOCK,
                "price_mode": FIXED_PRICE,
                "price": "5000000",
            },
            SERVER_NAME="localhost",
        )
        listing = HullListing.objects.get(shop=self.shop, eve_type=self.ship)
        self.assertEqual(listing.price, 5000000)
        self.assertEqual(listing.price_mode, FIXED_PRICE)

    @patch("shop.views.backoffice.jita_prices")
    def test_add_hull_pct_computes_now(self, mock_jp):
        mock_jp.return_value = {"buy": 100, "sell": 200}
        self.client.post(
            self._url(),
            {
                "eve_type": self.ship.id,
                "stock_mode": AUTO_STOCK,
                "price_mode": JITA_SELL_PCT,
                "price_pct": "50",
            },
            SERVER_NAME="localhost",
        )
        listing = HullListing.objects.get(shop=self.shop, eve_type=self.ship)
        self.assertEqual(listing.price_mode, JITA_SELL_PCT)
        self.assertEqual(listing.price, 100)  # 50% of sell 200


from shop.models import JITA_BUY_PCT  # noqa: E402


@no_manifest_static
class AddStockroomInheritanceTests(TestCase):
    def setUp(self):
        self.user, self.char = member_with_access()
        self.shop = Shop.objects.create(
            slug="s", name="S", created_by=self.user, enabled_sections=[HULL]
        )
        make_ship_sde_type(type_id=671, category_id=6)
        self.ship = make_ship_type()
        self.station = 60003760
        make_asset(
            self.char,
            type_id=self.ship.id,
            item_id=10,
            location_id=self.station,
            quantity=2,
        )
        self.client.force_login(self.user)

    def _post(self, **over):
        data = {
            "owner_id": self.char.id,
            "location_id": self.station,
            "price_mode": JITA_BUY_PCT,
            "price_pct": "95",
        }
        data.update(over)
        return self.client.post(
            reverse("backoffice:add_stockroom", args=[self.shop.pk, "hulls"]),
            data,
            SERVER_NAME="localhost",
        )

    @patch("shop.sections.hulls.handler.refresh_price_for_type")
    def test_first_seen_inherits_rule(self, mock_task):
        self._post()
        listing = HullListing.objects.get(shop=self.shop, eve_type=self.ship)
        self.assertEqual(listing.price_mode, JITA_BUY_PCT)
        self.assertEqual(listing.price_pct, Decimal("95.00"))
        mock_task.apply_async.assert_called_once()

    @patch("shop.sections.hulls.handler.refresh_price_for_type")
    def test_existing_listing_untouched(self, mock_task):
        HullListing.objects.create(
            shop=self.shop, eve_type=self.ship, price_mode=FIXED_PRICE, price=123
        )
        self._post()
        listing = HullListing.objects.get(shop=self.shop, eve_type=self.ship)
        self.assertEqual(listing.price_mode, FIXED_PRICE)  # unchanged
        self.assertEqual(listing.price, 123)
        mock_task.apply_async.assert_not_called()  # not first-seen

    @patch("shop.sections.hulls.handler.refresh_price_for_type")
    def test_ask_source_leaves_new_hull_at_fixed(self, mock_task):
        self._post(price_mode=FIXED_PRICE, price_pct="")
        listing = HullListing.objects.get(shop=self.shop, eve_type=self.ship)
        self.assertEqual(listing.price_mode, FIXED_PRICE)
        # Its own mode is fixed, so nothing to refresh.
        mock_task.apply_async.assert_not_called()

    @patch("shop.sections.hulls.handler.refresh_price_for_type")
    def test_existing_pct_hull_keeps_rule_and_is_not_re_enqueued_on_add(
        self, mock_task
    ):
        # A pct-mode hull already listed; adding a stockroom that stocks it keeps
        # its own pct rule. Adding a stockroom only enqueues a price refresh for
        # NEWLY-created listings — the existing pct hull is (re)priced by the
        # periodic price-refresh cycle, not by the add.
        HullListing.objects.create(
            shop=self.shop,
            eve_type=self.ship,
            price_mode=JITA_SELL_PCT,
            price_pct=Decimal("90"),
            price=0,
        )
        self._post(price_mode=FIXED_PRICE, price_pct="")
        listing = HullListing.objects.get(shop=self.shop, eve_type=self.ship)
        self.assertEqual(listing.price_mode, JITA_SELL_PCT)  # own rule kept
        mock_task.apply_async.assert_not_called()  # not newly created


@no_manifest_static
class AddStockroomContainerNameTests(TestCase):
    def setUp(self):
        self.user, self.char = member_with_access()
        self.shop = Shop.objects.create(
            slug="s", name="S", created_by=self.user, enabled_sections=[HULL]
        )
        self.station = 60003760
        ctype = make_container_type()
        self.container = make_asset(
            self.char,
            type_id=ctype.id,
            item_id=700,
            location_id=self.station,
            quantity=1,
            name="Corp Hangar",
        )
        self.client.force_login(self.user)

    def test_stores_container_name(self):
        self.client.post(
            reverse("backoffice:add_stockroom", args=[self.shop.pk, "hulls"]),
            {
                "owner_id": self.char.id,
                "location_id": self.station,
                "container_item_id": "700",
                "price_mode": "fixed",
            },
            SERVER_NAME="localhost",
        )
        src = self.shop.sources.get(section=HULL)
        self.assertEqual(src.container_name, "Corp Hangar")


@no_manifest_static
class EditStockroomTests(TestCase):
    def setUp(self):
        self.user, self.char = member_with_access()
        self.shop = Shop.objects.create(
            slug="s", name="S", created_by=self.user, enabled_sections=[HULL]
        )
        self.station = 60003760
        self.src = ShopSource.objects.create(
            shop=self.shop,
            section=HULL,
            character_audit=self.char,
            location_id=self.station,
        )
        self.client.force_login(self.user)

    def _url(self):
        return reverse("backoffice:edit_stockroom", args=[self.shop.pk, self.src.pk])

    def test_updates_rule(self):
        self.client.post(
            self._url(),
            {"price_mode": JITA_BUY_PCT, "price_pct": "85"},
            SERVER_NAME="localhost",
        )
        self.src.refresh_from_db()
        self.assertEqual(self.src.price_mode, JITA_BUY_PCT)
        self.assertEqual(self.src.price_pct, Decimal("85.00"))

    def test_does_not_reprice_existing_listing(self):
        ship = make_ship_type()
        listing = HullListing.objects.create(
            shop=self.shop, eve_type=ship, price_mode=FIXED_PRICE, price=999
        )
        self.client.post(
            self._url(),
            {"price_mode": JITA_BUY_PCT, "price_pct": "85"},
            SERVER_NAME="localhost",
        )
        listing.refresh_from_db()
        self.assertEqual(listing.price_mode, FIXED_PRICE)  # untouched
        self.assertEqual(listing.price, 999)

    def test_pct_validation_leaves_rule(self):
        self.client.post(
            self._url(),
            {"price_mode": JITA_BUY_PCT, "price_pct": ""},
            SERVER_NAME="localhost",
        )
        self.src.refresh_from_db()
        self.assertEqual(self.src.price_mode, FIXED_PRICE)  # unchanged (invalid form)

    def test_foreign_shop_forbidden(self):
        other, _ = member_with_access("intruder", char_id=2002)
        self.client.force_login(other)
        resp = self.client.post(
            self._url(), {"price_mode": FIXED_PRICE}, SERVER_NAME="localhost"
        )
        self.assertEqual(resp.status_code, 403)


@no_manifest_static
class ToggleFeaturedTests(TestCase):
    def setUp(self):
        self.user, self.char = member_with_access()
        self.shop = Shop.objects.create(
            slug="s", name="S", created_by=self.user, enabled_sections=[HULL]
        )
        self.ship = make_ship_type()
        self.listing = HullListing.objects.create(shop=self.shop, eve_type=self.ship)
        self.client.force_login(self.user)

    def _url(self):
        return reverse(
            "backoffice:toggle_featured",
            args=[self.shop.pk, "hulls", self.listing.pk],
        )

    def test_toggles_on_then_off(self):
        self.client.post(self._url(), SERVER_NAME="localhost")
        self.listing.refresh_from_db()
        self.assertTrue(self.listing.featured)
        self.client.post(self._url(), SERVER_NAME="localhost")
        self.listing.refresh_from_db()
        self.assertFalse(self.listing.featured)

    def test_redirects_to_manage(self):
        resp = self.client.post(self._url(), SERVER_NAME="localhost")
        self.assertRedirects(
            resp, reverse("backoffice:manage_section", args=[self.shop.pk, "hulls"])
        )

    def test_requires_can_manage(self):
        other, _ = member_with_access("intruder", char_id=2002)
        self.client.force_login(other)
        resp = self.client.post(self._url(), SERVER_NAME="localhost")
        self.assertEqual(resp.status_code, 403)

    def test_sets_and_clears_featured_at(self):
        self.client.post(self._url(), SERVER_NAME="localhost")
        self.listing.refresh_from_db()
        self.assertIsNotNone(self.listing.featured_at)
        self.client.post(self._url(), SERVER_NAME="localhost")
        self.listing.refresh_from_db()
        self.assertIsNone(self.listing.featured_at)


@no_manifest_static
class ManageSectionTests(TestCase):
    def setUp(self):
        self.user, self.char = member_with_access()
        self.shop = Shop.objects.create(
            slug="s",
            name="S",
            created_by=self.user,
            enabled_sections=["hulls", "modules"],
        )
        self.client.force_login(self.user)

    def test_manage_redirects_to_first_section(self):
        resp = self.client.get(
            reverse("backoffice:manage", args=[self.shop.pk]), SERVER_NAME="localhost"
        )
        self.assertRedirects(
            resp,
            reverse("backoffice:manage_section", args=[self.shop.pk, "hulls"]),
            fetch_redirect_response=False,
        )

    def test_hulls_section_renders_management(self):
        resp = self.client.get(
            reverse("backoffice:manage_section", args=[self.shop.pk, "hulls"]),
            SERVER_NAME="localhost",
        )
        self.assertEqual(resp.status_code, 200)
        self.assertContains(resp, "Add hull")
        # Table enhancer: search + sortable columns + pager mount.
        self.assertContains(resp, "data-table")
        self.assertContains(resp, 'data-sort="num"')
        self.assertContains(resp, "data-table-pager")

    def test_modules_section_renders(self):
        resp = self.client.get(
            reverse("backoffice:manage_section", args=[self.shop.pk, "modules"]),
            SERVER_NAME="localhost",
        )
        self.assertContains(resp, "Module listings")

    def test_disabled_section_404(self):
        resp = self.client.get(
            reverse("backoffice:manage_section", args=[self.shop.pk, "bpc"]),
            SERVER_NAME="localhost",
        )
        self.assertEqual(resp.status_code, 404)


@no_manifest_static
class BpcStockroomTests(TestCase):
    def setUp(self):
        self.user, self.char = member_with_access()
        self.shop = Shop.objects.create(
            slug="s", name="S", created_by=self.user, enabled_sections=["bpc"]
        )
        self.station = 60003760
        self.client.force_login(self.user)

    def test_add_bpc_stockroom_without_pricing(self):
        resp = self.client.post(
            reverse("backoffice:add_stockroom", args=[self.shop.pk, "bpc"]),
            {
                "owner_id": self.char.id,
                "location_id": self.station,
                "container_item_id": "",
            },
            SERVER_NAME="localhost",
        )
        self.assertRedirects(
            resp, reverse("backoffice:manage_section", args=[self.shop.pk, "bpc"])
        )
        self.assertEqual(self.shop.sources.filter(section="bpc").count(), 1)

    def test_manage_bpc_renders_types(self):
        from shop.models import BpcListing, BpcVariant

        bp_type = make_ship_type(type_id=12040, name="Rifter Blueprint")
        listing = BpcListing.objects.create(shop=self.shop, eve_type=bp_type)
        BpcVariant.objects.create(listing=listing, me=10, te=20, runs=50, stock=2)
        resp = self.client.get(
            reverse("backoffice:manage_section", args=[self.shop.pk, "bpc"]),
            SERVER_NAME="localhost",
        )
        self.assertEqual(resp.status_code, 200)
        self.assertContains(resp, "Rifter Blueprint")
        self.assertContains(resp, "Stockrooms")
        # Table enhancer: search + sortable columns + pager mount.
        self.assertContains(resp, "data-table")
        self.assertContains(resp, "data-table-pager")
        self.assertContains(resp, "data-sort-value")
        # Price form: live-denomination span + per-variant hide toggle.
        self.assertContains(resp, "bpc-denom")
        self.assertContains(resp, "bpc-hide-toggle")
        # Featured star toggle for the listing.
        self.assertContains(resp, "fa-star")
        self.assertContains(
            resp,
            reverse(
                "backoffice:toggle_featured", args=[self.shop.pk, "bpc", listing.pk]
            ),
        )

    def test_save_variant_prices(self):
        from shop.models import BpcListing, BpcVariant

        bp_type = make_ship_type(type_id=12040, name="Rifter Blueprint")
        listing = BpcListing.objects.create(shop=self.shop, eve_type=bp_type)
        v1 = BpcVariant.objects.create(listing=listing, me=10, te=20, runs=50, stock=2)
        v2 = BpcVariant.objects.create(listing=listing, me=0, te=0, runs=10, stock=1)
        resp = self.client.post(
            reverse("backoffice:save_bpc_prices", args=[self.shop.pk, listing.pk]),
            {f"price_{v1.pk}": "25000000", f"price_{v2.pk}": "0"},
            SERVER_NAME="localhost",
        )
        self.assertRedirects(
            resp, reverse("backoffice:manage_section", args=[self.shop.pk, "bpc"])
        )
        v1.refresh_from_db()
        v2.refresh_from_db()
        self.assertEqual((v1.price, v2.price), (25000000, 0))
        # price_per_run is derived on save: 25M / 50 runs = 500K.
        self.assertEqual(v1.price_per_run, 500000)
        self.assertEqual(v2.price_per_run, 0)

    def test_save_variant_hide_toggle(self):
        from shop.models import BpcListing, BpcVariant

        bp_type = make_ship_type(type_id=12040, name="Rifter Blueprint")
        listing = BpcListing.objects.create(shop=self.shop, eve_type=bp_type)
        v1 = BpcVariant.objects.create(listing=listing, me=10, te=20, runs=50, stock=2)
        url = reverse("backoffice:save_bpc_prices", args=[self.shop.pk, listing.pk])
        # Checkbox present -> hidden.
        self.client.post(
            url,
            {f"price_{v1.pk}": "0", f"hidden_{v1.pk}": "on"},
            SERVER_NAME="localhost",
        )
        v1.refresh_from_db()
        self.assertTrue(v1.hidden)
        # Checkbox absent -> visible again.
        self.client.post(url, {f"price_{v1.pk}": "0"}, SERVER_NAME="localhost")
        v1.refresh_from_db()
        self.assertFalse(v1.hidden)

    def test_toggle_bpc_featured(self):
        from shop.models import BpcListing

        bp_type = make_ship_type(type_id=12040, name="Rifter Blueprint")
        listing = BpcListing.objects.create(shop=self.shop, eve_type=bp_type)
        url = reverse(
            "backoffice:toggle_featured", args=[self.shop.pk, "bpc", listing.pk]
        )
        self.client.post(url, SERVER_NAME="localhost")
        listing.refresh_from_db()
        self.assertTrue(listing.featured)
        self.assertIsNotNone(listing.featured_at)
        self.client.post(url, SERVER_NAME="localhost")
        listing.refresh_from_db()
        self.assertFalse(listing.featured)
        self.assertIsNone(listing.featured_at)
