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

from .base import FunctionalTest
from apps.applets.models import Applet
from apps.lyric.models import Token, User


class CarteBlancheTest(FunctionalTest):

    def setUp(self):
        super().setUp()
        for slug, name, cols, rows in [
            ("new-game", "New Game", 6, 3),
            ("my-games", "My Games", 6, 3),
            ("game-kit", "Game Kit", 6, 3),
        ]:
            Applet.objects.get_or_create(
                slug=slug,
                defaults={
                    "name": name, "grid_cols": cols,
                    "grid_rows": rows, "context": "gameboard",
                },
            )
        # is_staff triggers COIN + FREE + PASS via post_save signal; PASS auto-equipped
        self.gamer = User.objects.create(email="blanche@test.io", is_staff=True)
        Token.objects.create(user=self.gamer, token_type=Token.TITHE)
        self.carte = Token.objects.create(user=self.gamer, token_type=Token.CARTE)

    # ── Test 1 ───────────────────────────────────────────────────────────────

    def test_equipped_pass_shows_mini_tooltip_in_game_kit(self):
        # 1. Log in, land on dashboard
        self.create_pre_authenticated_session("blanche@test.io")
        self.browser.get(self.live_server_url)
        # 2. Open kit bag — Backstage Pass visible in Trinkets section
        self.wait_for(lambda: 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-type="{Token.PASS}"]',
            )
        )
        # 3. Navigate to gameboard (use get() — kit bag dialog still open and
        #    would intercept a click on the footer nav link in headless Firefox)
        self.browser.get(self.live_server_url + "/gameboard/")
        self.wait_for(
            lambda: self.assertRegex(self.browser.current_url, r"/gameboard/$")
        )
        # 4. Find Backstage Pass in the Game Kit applet
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
        pass_el = self.browser.find_element(By.ID, "id_kit_pass")
        self.browser.execute_script(
            "arguments[0].scrollIntoView({block: 'center'})", pass_el
        )
        # 5. Hover over Pass — main tooltip appears via portal
        ActionChains(self.browser).move_to_element(pass_el).perform()
        self.wait_for(
            lambda: self.assertTrue(
                self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
            )
        )
        portal = self.browser.find_element(By.ID, "id_tooltip_portal")
        self.assertIn("Backstage Pass", portal.text)
        self.assertIn("Admit All Entry", portal.text)
        # 6. A mini tooltip appears below the main tooltip, flush with its right edge.
        #    Since Pass is the equipped trinket, it says "Equipped."
        mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
        self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
        self.assertIn("Equipped", mini.text)
        portal_rect = self.browser.execute_script(
            "return arguments[0].getBoundingClientRect()", portal
        )
        mini_rect = self.browser.execute_script(
            "return arguments[0].getBoundingClientRect()", mini
        )
        self.assertGreater(mini_rect["top"], portal_rect["bottom"] - 5)   # below main
        self.assertAlmostEqual(mini_rect["right"], portal_rect["right"], delta=10)  # flush right

    # ── Test 2 ───────────────────────────────────────────────────────────────

    def test_carte_blanche_equip_and_multi_slot_gatekeeper(self):
        # 1. Log in, navigate directly to gameboard
        self.create_pre_authenticated_session("blanche@test.io")
        self.browser.get(self.live_server_url + "/gameboard/")
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))

        # 2. Hover over Free Token — no mini tooltip (not a trinket, no data-token-id)
        el = self.browser.find_element(By.ID, "id_kit_free_token")
        ActionChains(self.browser).move_to_element(el).perform()
        self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
        )
        self.assertFalse(
            self.browser.find_element(By.ID, "id_mini_tooltip_portal").is_displayed()
        )
        # Coin-on-a-String IS equippable (has data-token-id) — mini tooltip shows
        coin_el = self.browser.find_element(By.ID, "id_kit_coin_on_a_string")
        ActionChains(self.browser).move_to_element(coin_el).perform()
        self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
        )
        self.wait_for(
            lambda: self.assertTrue(
                self.browser.find_element(By.ID, "id_mini_tooltip_portal").is_displayed()
            )
        )

        # 3. Hover Carte Blanche — main tooltip present; mini shows "Not Equipped"; DON active
        carte_el = self.browser.find_element(By.ID, "id_kit_carte_blanche")
        self.browser.execute_script(
            "arguments[0].scrollIntoView({block: 'center'})", carte_el
        )
        ActionChains(self.browser).move_to_element(carte_el).perform()
        self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
        )
        portal = self.browser.find_element(By.ID, "id_tooltip_portal")
        self.assertIn("Carte Blanche", portal.text)
        self.assertIn("Admit up to +6", portal.text)
        self.assertIn("taking over from here", portal.text)
        self.assertIn("no expiry", portal.text)
        mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
        self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
        self.assertIn("Not Equipped", mini.text)
        don = portal.find_element(By.CSS_SELECTOR, ".btn-equip")
        self.assertNotIn("btn-disabled", don.get_attribute("class"))

        # 4. Click DON — DON becomes disabled; data-equipped-id set optimistically
        don.click()
        self.wait_for(
            lambda: self.assertIn(
                "btn-disabled",
                portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"),
            )
        )

        # 5. JS-side optimistic update: data-equipped-id reflects Carte immediately
        game_kit = self.browser.find_element(By.ID, "id_game_kit")
        self.wait_for(
            lambda: self.assertEqual(
                game_kit.get_attribute("data-equipped-id"),
                str(self.carte.pk),
            )
        )

        # 6. Hover Backstage Pass — mini shows "Not Equipped" (Pass no longer equipped)
        pass_el = self.browser.find_element(By.ID, "id_kit_pass")
        ActionChains(self.browser).move_to_element(pass_el).perform()
        self.wait_for(
            lambda: self.assertIn(
                "Backstage Pass",
                self.browser.find_element(By.ID, "id_tooltip_portal").text,
            )
        )
        mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
        self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
        self.assertIn("Not Equipped", mini.text)

        # ── GATEKEEPER PHASE ─────────────────────────────────────────────────

        # 8. Create a new game room via the New Game applet
        self.browser.find_element(By.ID, "id_new_game_name").send_keys("The Long Room")
        self.browser.find_element(By.ID, "id_create_game_btn").click()
        self.wait_for(
            lambda: self.assertIn("/gameboard/room/", self.browser.current_url)
        )
        self.wait_for(
            lambda: self.assertIn("/gate/", self.browser.current_url)
        )
        gate_url = self.browser.current_url
        self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-overlay")
        )

        def open_kit_and_select_carte():
            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-type="{Token.CARTE}"]',
                )
            )
            self.browser.find_element(
                By.CSS_SELECTOR,
                f'#id_kit_bag_dialog [data-token-type="{Token.CARTE}"]',
            ).click()

        def deposit_carte():
            self.browser.find_element(By.CSS_SELECTOR, "button.token-rails").click()
            self.wait_for(
                lambda: self.browser.find_element(By.CSS_SELECTOR, ".token-slot.claimed")
            )

        # 9. Open kit bag, select Carte Blanche, deposit via rails btn
        open_kit_and_select_carte()
        deposit_carte()

        # Helper: always fetches a fresh gate-slot element (avoid stale refs after redirects)
        def get_circle(i):
            return self.browser.find_element(
                By.CSS_SELECTOR, f".gate-slot[data-slot='{i + 1}']"
            )

        # 10. Return panel glows; circle 1 gains OK btn → click it → fills
        return_panel = self.browser.find_element(By.CSS_SELECTOR, ".token-slot.claimed")
        self.assertTrue(return_panel.is_displayed())
        self.wait_for(
            lambda: get_circle(0).find_element(By.CSS_SELECTOR, ".drop-token-btn")
        ).click()
        self.wait_for(
            lambda: self.assertIn("filled", get_circle(0).get_attribute("class"))
        )

        # 11. Return panel still glows; circle 2 now has OK btn
        self.assertTrue(
            self.browser.find_element(By.CSS_SELECTOR, ".token-slot.claimed").is_displayed()
        )
        self.wait_for(lambda: get_circle(1).find_element(By.CSS_SELECTOR, ".drop-token-btn"))

        # 12. Kit bag trinket section is now empty — Carte Blanche unequipped on deposit
        self.browser.find_element(By.ID, "id_kit_btn").click()
        self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR, "#id_kit_bag_dialog .kit-bag-placeholder"
            )
        )
        self.browser.find_element(By.ID, "id_kit_btn").click()
        self.wait_for(
            lambda: self.assertFalse(
                self.browser.find_element(By.ID, "id_kit_bag_dialog").is_displayed()
            )
        )

        # 13. Cold feet: click return panel → Carte returned, return panel gone
        self.browser.find_element(By.CSS_SELECTOR, ".token-return-btn").click()
        self.wait_for(
            lambda: self.assertEqual(
                len(self.browser.find_elements(By.CSS_SELECTOR, ".token-slot.claimed")), 0
            )
        )
        # Carte reappears in Game Kit with DON available — not re-equipped automatically.
        # Game Kit is only in the /gameboard/ context, not the gatekeeper page.
        self.browser.get(self.live_server_url + "/gameboard/")
        carte_el = self.browser.find_element(By.ID, "id_kit_carte_blanche")
        self.browser.execute_script(
            "arguments[0].scrollIntoView({block:'center'})", carte_el
        )
        ActionChains(self.browser).move_to_element(carte_el).perform()
        portal = self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_tooltip_portal")
        )
        self.wait_for(lambda: self.assertTrue(portal.is_displayed()))
        don = self.wait_for(
            lambda: portal.find_element(By.CSS_SELECTOR, ".btn-equip")
        )
        self.assertNotIn("btn-disabled", don.get_attribute("class"))

        # ── COLD FEET RESOLVED: full six-slot run ────────────────────────────

        # 14. Re-equip Carte via portal DON, navigate back to gate
        don.click()
        self.wait_for(
            lambda: self.assertIn(
                "btn-disabled",
                portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"),
            )
        )
        self.browser.get(gate_url)
        self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-overlay"))
        open_kit_and_select_carte()
        deposit_carte()

        # 15. Click OK on circle 1 → fills; circle 1 loses its OK btn; circle 2 gains one
        self.wait_for(
            lambda: get_circle(0).find_element(By.CSS_SELECTOR, ".drop-token-btn")
        ).click()
        self.wait_for(
            lambda: self.assertIn("filled", get_circle(0).get_attribute("class"))
        )
        self.wait_for(
            lambda: self.assertEqual(
                len(get_circle(0).find_elements(By.CSS_SELECTOR, ".drop-token-btn")), 0
            )
        )
        self.wait_for(lambda: get_circle(1).find_element(By.CSS_SELECTOR, ".drop-token-btn"))

        # 16. Click OK on circle 2 → turns to NVM; circle 3 gains OK
        self.wait_for(
            lambda: get_circle(1).find_element(By.CSS_SELECTOR, ".drop-token-btn")
        ).click()
        self.wait_for(
            lambda: self.assertEqual(
                get_circle(1).find_element(By.CSS_SELECTOR, ".slot-release-btn").text.upper(),
                "NVM",
            )
        )
        self.wait_for(lambda: get_circle(2).find_element(By.CSS_SELECTOR, ".drop-token-btn"))

        # 17. Click NVM on circle 2 → circle 2 regains OK; circle 3 loses it (strict n+1)
        #     Circle 1 still filled; return panel still glowing; lease name still present
        self.wait_for(
            lambda: get_circle(1).find_element(By.CSS_SELECTOR, ".slot-release-btn")
        ).click()
        self.wait_for(lambda: get_circle(1).find_element(By.CSS_SELECTOR, ".drop-token-btn"))
        self.assertIn("filled", get_circle(0).get_attribute("class"))
        self.assertTrue(
            self.browser.find_element(By.CSS_SELECTOR, ".token-slot.claimed").is_displayed()
        )
        self.assertEqual(
            len(get_circle(2).find_elements(By.CSS_SELECTOR, ".drop-token-btn")), 0
        )

        # 18. Fill circles 2 → 6 sequentially; verify each subsequent OK appears
        for i in range(1, 6):
            self.wait_for(
                lambda i=i: get_circle(i).find_element(By.CSS_SELECTOR, ".drop-token-btn")
            ).click()
            self.wait_for(
                lambda i=i: self.assertIn("filled", get_circle(i).get_attribute("class"))
            )
            if i < 5:
                self.wait_for(
                    lambda i=i: get_circle(i + 1).find_element(
                        By.CSS_SELECTOR, ".drop-token-btn"
                    )
                )

        # 19. All six circles filled — launch btn appears
        self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".launch-game-btn")
        )

    def test_carte_in_use_game_kit_shows_room_attribution(self):
        """While Carte Blanche is deposited in a room, its Game Kit token carries
        the room name on data-current-room-name so the mini-portal can render
        'In-Use: <room name>' on hover."""
        self.create_pre_authenticated_session("blanche@test.io")
        self.browser.get(self.live_server_url + "/gameboard/")
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))

        # DON the Carte Blanche via tooltip portal
        carte_el = self.browser.find_element(By.ID, "id_kit_carte_blanche")
        self.browser.execute_script(
            "arguments[0].scrollIntoView({block:'center'})", carte_el
        )
        ActionChains(self.browser).move_to_element(carte_el).perform()
        portal = self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_tooltip_portal")
        )
        self.wait_for(lambda: self.assertTrue(portal.is_displayed()))
        don = self.wait_for(lambda: portal.find_element(By.CSS_SELECTOR, ".btn-equip"))
        don.click()
        self.wait_for(
            lambda: self.assertIn(
                "btn-disabled",
                portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"),
            )
        )

        # Create a room and deposit the Carte Blanche
        self.browser.find_element(By.ID, "id_new_game_name").send_keys("Commitment Room")
        self.browser.find_element(By.ID, "id_create_game_btn").click()
        self.wait_for(lambda: self.assertIn("/gate/", self.browser.current_url))

        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-type="{Token.CARTE}"]',
            )
        ).click()
        self.browser.find_element(By.CSS_SELECTOR, "button.token-rails").click()
        self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".token-slot.claimed")
        )

        # Game Kit panel is on /gameboard/, not the gate page — navigate back to check token
        self.browser.get(self.live_server_url + "/gameboard/")
        carte_el = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_kit_carte_blanche")
        )
        self.assertEqual(
            "Commitment Room",
            carte_el.get_attribute("data-current-room-name"),
        )
