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

from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse

from allianceauth.tests.auth_utils import AuthUtils
from memberaudit.models import CharacterAsset, Location

from shop.models import HULL, HullListing, Shop, ShopSource
from shop.tests.test_stock import make_owner_character, 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])
        self.ship = make_ship_type()
        self.station = Location.objects.create(id=60003760, name="Jita IV-4")
        self.client.force_login(self.user)

    def _asset(self, item_id, qty=1):
        return CharacterAsset.objects.create(
            character=self.char, item_id=item_id, eve_type=self.ship, is_singleton=True,
            location_flag="Hangar", quantity=qty, location=self.station,
        )

    # --- 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_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)
        CharacterAsset.objects.create(
            character=self.char, item_id=99, eve_type=module, is_singleton=False,
            location_flag="Hangar", quantity=3, location=self.station,
        )
        resp = self.client.post(
            reverse("backoffice:add_stockroom", args=[self.shop.pk]),
            {"character_id": self.char.id, "location_id": self.station.id, "container_item_id": "",
             "price_mode": "fixed"},
            SERVER_NAME="localhost",
        )
        self.assertRedirects(resp, reverse("backoffice:manage", args=[self.shop.pk]))
        self.assertEqual(self.shop.sources.filter(section=HULL).count(), 1)
        # exactly one hull listing (the Erebus), with computed stock; the module is 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=self.char, location=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", args=[self.shop.pk]))
        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=self.char, location=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]),
            {"character_id": 999999, "location_id": self.station.id, "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=self.char, location=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", args=[self.shop.pk]))
        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", args=[self.shop.pk]))
        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])
        self.ship = make_ship_type()
        self.station = Location.objects.create(id=60003760, name="Jita IV-4")
        CharacterAsset.objects.create(
            character=self.char, item_id=10, eve_type=self.ship, is_singleton=True,
            location_flag="Hangar", quantity=2, location=self.station,
        )
        self.client.force_login(self.user)

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

    @patch("shop.views.backoffice.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.views.backoffice.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.views.backoffice.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.views.backoffice.refresh_price_for_type")
    def test_existing_pct_hull_repriced_on_add(self, mock_task):
        # A pct-mode hull already listed; adding a stockroom that stocks it (even
        # an Ask source) should refresh its price — it keeps its own pct rule.
        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_called_once()


@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 = Location.objects.create(id=60003760, name="Jita IV-4")
        from shop.tests.test_stock import make_container_type
        ctype = make_container_type()
        self.container = CharacterAsset.objects.create(
            character=self.char, item_id=700, eve_type=ctype, is_singleton=True,
            location_flag="Hangar", quantity=1, location=self.station, 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]),
            {"character_id": self.char.id, "location_id": self.station.id,
             "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 = Location.objects.create(id=60003760, name="Jita IV-4")
        self.src = ShopSource.objects.create(
            shop=self.shop, section=HULL, character=self.char, location=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, 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", args=[self.shop.pk]))

    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)
