from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By

from .base import FunctionalTest
from .sig_page import _seed_earthman_sig_pile
from apps.applets.models import Applet
from apps.epic.models import DeckVariant, Room
from apps.lyric.models import Token, User


class GameboardNavigationTest(FunctionalTest):
    def setUp(self):
        super().setUp()
        # Re-seed the Earthman DeckVariant up front — TransactionTestCase
        # flushes migration data between tests, so by the time the user
        # is created (`create_pre_authenticated_session` inside each test
        # method), the post_save signal in `apps/lyric/models.py:537`'s
        # `DeckVariant.objects.filter(slug="earthman").first()` would
        # return None + skip `unlocked_decks.add(earthman)`. That left
        # `test_game_kit_panel_shows_token_inventory`'s `id_kit_earthman_
        # deck` selector unrenderable (applet falls through to the
        # `{% empty %}` `id_kit_card_deck` placeholder). CI #340 trap.
        # See [[feedback-transactiontestcase-flush]].
        _seed_earthman_sig_pile()
        Applet.objects.get_or_create(slug="new-game", defaults={"name": "New Game", "context": "gameboard"})
        Applet.objects.get_or_create(slug="my-games", defaults={"name": "My Games", "context": "gameboard"})
        Applet.objects.get_or_create(slug="game-kit", defaults={"name": "Game Kit", "context": "gameboard"})

    def test_footer_links_to_gameboard(self):
        # 1. Log in, nav to dashboard
        self.create_pre_authenticated_session("capman@test.io")
        self.browser.get(self.live_server_url)
        # 2. Assert footer nav present w. dash- & gameboard tabs
        self.browser.find_element(By.ID, "id_footer_nav")
        self.browser.find_element(By.CSS_SELECTOR, '#id_footer_nav a[href="/"]')
        self.browser.find_element(By.CSS_SELECTOR, '#id_footer_nav a[href="/gameboard/"]')
        # 3. Click the gameboard tab
        self.browser.find_element(
            By.CSS_SELECTOR, '#id_footer_nav a[href="/gameboard/"]'
        ).click()
        # 4. Assert user landed on gameboard
        self.wait_for(
            lambda: self.assertRegex(self.browser.current_url, r"/gameboard/$")
        )

    def test_gameboard_shows_game_applets(self):
        # 1. Log in, nav directly to gameboard
        self.create_pre_authenticated_session("capman@test.io")
        self.browser.get(self.live_server_url + "/gameboard/")
        # 2. Assert My Games applet present
        self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_applet_my_games")
        )
        # 3. Assert no games listed yet for new user
        my_games = self.browser.find_element(By.ID, "id_applet_my_games")
        game_items = my_games.find_elements(By.CSS_SELECTOR, ".game-item")
        self.assertEqual(len(game_items), 0)
        # 4. Assert New Game applet present
        self.browser.find_element(By.ID, "id_applet_new_game")

    def test_game_kit_panel_shows_token_inventory(self):
        # 1. Log in, nav to gameboard
        self.create_pre_authenticated_session("capman@test.io")
        self.browser.get(self.live_server_url + "/gameboard/")
        # 2. Assert game kit applet & gear btn present
        self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_game_kit")
        )
        self.browser.find_element(By.CSS_SELECTOR, ".gear-btn")
        # 3. Assert Coin-on-a-String present in kit
        coin = self.browser.find_element(By.ID, "id_kit_coin_on_a_string")
        self.browser.execute_script("arguments[0].scrollIntoView({block: 'center'});", coin)
        # 6. Hover over it; assert tooltip shows name, entry text & reuse description
        ActionChains(self.browser).move_to_element(coin).perform()
        self.wait_for(
            lambda: self.assertTrue(
                self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
            )
        )
        coin_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text
        self.assertIn("Coin-on-a-String", coin_tooltip)
        self.assertIn("Admit 1 Entry", coin_tooltip)
        self.assertIn("and another after that", coin_tooltip)
        # 7. (REMOVED 2026-05-26) Free Token used to live in Game Kit applet
        # — moved to /dashboard/'s My Wallet applet per spec ("only equippables
        # in Game Kit"). Free Token tooltip coverage now lives on /dashboard/.
        # 8. NEGATIVE assertion: Free Token element must NOT exist on Game Kit
        # — regression guard against accidentally re-adding non-equippable
        # items here.
        self.assertEqual(
            len(self.browser.find_elements(By.ID, "id_kit_free_token")), 0,
            "Free Token must NOT render in the Game Kit applet — it's not "
            "equippable; lives in My Wallet applet on /dashboard/",
        )
        # 9. Assert card deck + dice set present. capman has Earthman unlocked
        # via the post_save signal (`apps/lyric/models.py:540`), so the
        # `id_kit_card_deck` empty-state placeholder doesn't render — the
        # concrete `id_kit_earthman_deck` does instead. (The `id_kit_card_
        # deck` placeholder was previously asserted here, which was a stale
        # check predating the auto-unlock signal — caught 2026-05-26 by the
        # CI run that surfaced it after the Free Token relocation moved this
        # test's failure point past step 7.)
        self.browser.find_element(By.ID, "id_kit_earthman_deck")
        self.browser.find_element(By.ID, "id_kit_dice_set")


class GameboardAppletMenuTest(FunctionalTest):
    def setUp(self):
        super().setUp()
        Applet.objects.get_or_create(slug="new-game", defaults={"name": "New Game", "context": "gameboard"})
        Applet.objects.get_or_create(slug="my-games", defaults={"name": "My Games", "context": "gameboard"})
        self.create_pre_authenticated_session("gamer@test.io")
        self.browser.get(self.live_server_url + "/gameboard/")

    def test_user_can_toggle_applet_visibility_via_gear_menu(self):
        # 1. Assert both applets present
        self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_applet_my_games")
        )
        self.browser.find_element(By.ID, "id_applet_new_game")
        # 2. Click gear; wait for menu
        self.browser.find_element(By.CSS_SELECTOR, ".gear-btn").click()
        self.wait_for(
            lambda: self.assertTrue(
                self.browser.find_element(By.ID, "id_game_applet_menu").is_displayed()
            )
        )
        # 3. Find checkboxes; assert both checked
        menu = self.browser.find_element(By.ID, "id_game_applet_menu")
        my_games_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="my-games"]')
        new_game_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="new-game"]')
        self.assertTrue(my_games_cb.is_selected())
        self.assertTrue(new_game_cb.is_selected())
        # 4. Uncheck my-games; plant no-reload marker; submit
        my_games_cb.click()
        self.assertFalse(my_games_cb.is_selected())
        self.browser.execute_script("window.__no_reload_marker = true")
        menu.find_element(By.CSS_SELECTOR, '[type="submit"]').click()
        # 5. Wait for menu to close; assert my-games gone, new game remains
        self.wait_for(
            lambda: self.assertFalse(
                self.browser.find_element(By.ID, "id_game_applet_menu").is_displayed()
            )
        )
        self.wait_for(
            lambda: self.assertRaises(
                NoSuchElementException,
                self.browser.find_element,
                By.ID, "id_applet_my_games",
            )
        )
        self.browser.find_element(By.ID, "id_applet_new_game")
        # 6. Re-check my-games; assert it reappears
        self.browser.find_element(By.CSS_SELECTOR, ".gear-btn").click()
        self.wait_for(
            lambda: self.assertTrue(
                self.browser.find_element(By.ID, "id_game_applet_menu").is_displayed()
            )
        )
        menu = self.browser.find_element(By.ID, "id_game_applet_menu")
        my_games_cb = menu.find_element(By.CSS_SELECTOR, '[name="applets"][value="my-games"]')
        self.assertFalse(my_games_cb.is_selected())
        my_games_cb.click()
        menu.find_element(By.CSS_SELECTOR, '[type="submit"]').click()
        self.wait_for(
            lambda: self.assertFalse(
                self.browser.find_element(By.ID, "id_game_applet_menu").is_displayed()
            )
        )
        self.wait_for(
            lambda: self.assertTrue(
                self.browser.find_element(By.ID, "id_applet_my_games")
            )
        )
        # 7. Assert no full page reload occurred
        self.assertTrue(self.browser.execute_script("return window.__no_reload_marker === true"))


class GameKitEquipTest(FunctionalTest):
    """DON|DOFF equip buttons in the game kit applet portal tooltip."""

    def setUp(self):
        super().setUp()
        self.earthman, _ = DeckVariant.objects.get_or_create(
            slug="earthman",
            defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True},
        )
        Applet.objects.get_or_create(slug="game-kit", defaults={"name": "Game Kit", "context": "gameboard"})
        self.create_pre_authenticated_session("gamer@equip.io")
        self.gamer = User.objects.get(email="gamer@equip.io")
        # Promote to staff so the pass_token appears in the game kit applet.
        self.gamer.is_staff = True
        self.gamer.save(update_fields=["is_staff"])
        self.gamer.unlocked_decks.add(self.earthman)
        self.coin = self.gamer.tokens.filter(token_type=Token.COIN).first()
        # Create a PASS token manually — starts unequipped (coin is auto-equipped).
        self.pass_token = Token.objects.create(user=self.gamer, token_type=Token.PASS)
        self.browser.set_window_size(1200, 900)
        self.browser.get(self.live_server_url + "/gameboard/")

    def _hover_game_kit_token(self, token_el):
        """Hover token, wait for portal, return portal element."""
        ActionChains(self.browser).move_to_element(token_el).perform()
        self.wait_for(
            lambda: self.assertTrue(
                self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
            )
        )
        return self.browser.find_element(By.ID, "id_tooltip_portal")

    def test_unequipped_token_shows_don_active_doff_disabled(self):
        """Backstage Pass — naturally unequipped (coin is auto-equipped): DON active, DOFF is ×."""
        pass_el = self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_kit_pass")
        )
        portal = self._hover_game_kit_token(pass_el)
        don = portal.find_element(By.CSS_SELECTOR, ".btn-equip")
        doff = portal.find_element(By.CSS_SELECTOR, ".btn-unequip")
        self.assertNotIn("btn-disabled", don.get_attribute("class"))
        self.assertIn("btn-disabled", doff.get_attribute("class"))
        self.assertEqual(doff.text, "×")

    def test_equipped_token_shows_doff_active_don_disabled(self):
        """Auto-equipped coin: DOFF active, DON is ×; mini-portal says Equipped."""
        coin = self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_kit_coin_on_a_string")
        )
        portal = self._hover_game_kit_token(coin)
        don = portal.find_element(By.CSS_SELECTOR, ".btn-equip")
        doff = portal.find_element(By.CSS_SELECTOR, ".btn-unequip")
        self.assertIn("btn-disabled", don.get_attribute("class"))
        self.assertEqual(don.text, "×")
        self.assertNotIn("btn-disabled", doff.get_attribute("class"))
        mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
        self.assertEqual(mini.text, "Equipped")
        self.assertEqual(len(mini.find_elements(By.CSS_SELECTOR, "button")), 0)

    def test_doff_then_don_roundtrip(self):
        """Full roundtrip: DOFF unequips (portal stays open, buttons swap, mini updates);
        DON re-equips (buttons swap back, mini updates back, DB confirms)."""
        coin = self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_kit_coin_on_a_string")
        )
        portal = self._hover_game_kit_token(coin)

        # — DOFF —
        portal.find_element(By.CSS_SELECTOR, ".btn-unequip").click()
        self.wait_for(
            lambda: self.assertIn(
                "btn-disabled",
                portal.find_element(By.CSS_SELECTOR, ".btn-unequip").get_attribute("class"),
            )
        )
        self.assertEqual(portal.find_element(By.CSS_SELECTOR, ".btn-unequip").text, "×")
        self.assertNotIn(
            "btn-disabled",
            portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"),
        )
        self.assertEqual(
            self.browser.find_element(By.ID, "id_mini_tooltip_portal").text, "Not Equipped"
        )
        self.gamer.refresh_from_db()
        self.assertIsNone(self.gamer.equipped_trinket)

        # — DON —
        portal.find_element(By.CSS_SELECTOR, ".btn-equip").click()
        self.wait_for(
            lambda: self.assertIn(
                "btn-disabled",
                portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"),
            )
        )
        self.assertEqual(portal.find_element(By.CSS_SELECTOR, ".btn-equip").text, "×")
        self.assertNotIn(
            "btn-disabled",
            portal.find_element(By.CSS_SELECTOR, ".btn-unequip").get_attribute("class"),
        )
        self.assertEqual(
            self.browser.find_element(By.ID, "id_mini_tooltip_portal").text, "Equipped"
        )
        self.gamer.refresh_from_db()
        self.assertEqual(self.gamer.equipped_trinket, self.coin)

    def test_doff_updates_open_kit_bag_dialog(self):
        """DOFF from game kit applet replaces trinket card in currently-open kit bag dialog."""
        self.gamer.equipped_trinket = self.coin
        self.gamer.save(update_fields=["equipped_trinket"])
        self.browser.refresh()
        # Open kit bag dialog
        self.browser.find_element(By.ID, "id_kit_btn").click()
        self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR,
                f'#id_kit_bag_dialog [data-token-id="{self.coin.id}"]',
            )
        )
        # While dialog is open, hover coin and DOFF from game kit
        coin = self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_kit_coin_on_a_string")
        )
        portal = self._hover_game_kit_token(coin)
        portal.find_element(By.CSS_SELECTOR, ".btn-unequip").click()
        # Dialog updates: trinket card replaced with placeholder
        self.wait_for(
            lambda: self.assertEqual(
                len(self.browser.find_elements(
                    By.CSS_SELECTOR,
                    f'#id_kit_bag_dialog [data-token-id="{self.coin.id}"]',
                )), 0
            )
        )
        self.browser.find_element(By.CSS_SELECTOR, "#id_kit_bag_dialog .kit-bag-placeholder")
