"""
Component FT — tray tooltip (Phase 1: role-card img).

Hovering the tray's role-card <img> populates #id_tooltip_portal with the
role's display name as the title and a description, matching the existing
Game-Kit tooltip portal pattern (apps.tooltips). Mousing well off the card
clears the portal.

Phase 2 (sig-card tooltip mirroring #id_fan_fyi_panel) lives in a separate
test class and is not yet covered here.
"""
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.epic.models import DeckVariant, Room, TableSeat, TarotCard
from apps.lyric.models import User


from .room_page import _equip_earthman_deck, _fill_room_via_orm


class TrayRoleCardTooltipTest(FunctionalTest):
    EMAILS = [
        "tt1@test.io", "tt2@test.io", "tt3@test.io",
        "tt4@test.io", "tt5@test.io", "tt6@test.io",
    ]

    def setUp(self):
        super().setUp()
        self.browser.set_window_size(800, 1200)
        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"}
        )

    def _make_room_with_role(self, role="PC"):
        """Room in ROLE_SELECT with founder seated and assigned `role`."""
        founder, _ = User.objects.get_or_create(email=self.EMAILS[0])
        _equip_earthman_deck(founder)
        room = Room.objects.create(name="Tooltip Room", owner=founder)
        _fill_room_via_orm(room, self.EMAILS)
        room.table_status = Room.ROLE_SELECT
        room.save()
        for slot in room.gate_slots.order_by("slot_number"):
            TableSeat.objects.create(
                room=room, gamer=slot.gamer, slot_number=slot.slot_number,
                role=(role if slot.slot_number == 1 else None),
            )
        return room

    def _open_tray(self):
        # Tray is closed off-screen by default. Open it AND wait for the slide
        # transition (left: 736px → 0px, ~0.35s) to land before hovering;
        # otherwise getBoundingClientRect returns the closed-state rect that
        # ActionChains sees as off-viewport.
        self.browser.execute_script("Tray.open();")
        self.wait_for(
            lambda: self.assertLess(
                self.browser.execute_script(
                    "return document.getElementById('id_tray_wrap').getBoundingClientRect().left"
                ),
                40,
                "tray wrap should slide to ~0",
            )
        )

    def _hover(self, element):
        ActionChains(self.browser).move_to_element(element).perform()

    def _move_far_away(self):
        # Synthesize a mousemove event well outside the trigger+portal union.
        # ActionChains origins require a visible element; firing the event
        # directly via JS sidesteps that and exercises the same listener path.
        self.browser.execute_script(
            "document.dispatchEvent(new MouseEvent('mousemove',"
            "{bubbles:true, clientX:9999, clientY:9999}));"
        )

    # ------------------------------------------------------------------ #
    # T1 — Hover role img → portal shows title (role display name)        #
    #     + description ("[Placeholder description]").                    #
    # ------------------------------------------------------------------ #

    def test_hover_role_img_populates_tooltip_portal(self):
        room = self._make_room_with_role(role="PC")
        self.create_pre_authenticated_session(self.EMAILS[0])
        self.browser.get(f"{self.live_server_url}/gameboard/room/{room.id}/gate/")

        self._open_tray()
        # The role card is rendered server-side as the first tray cell.
        role_img = self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR, "#id_tray_grid .tray-role-card img"
            )
        )

        # Portal exists at page root, hidden initially.
        portal = self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_tooltip_portal")
        )
        self.assertFalse(
            portal.is_displayed(),
            "tooltip portal should start hidden before any hover",
        )

        # Hover the role art → portal becomes visible with title + description.
        self._hover(role_img)
        self.wait_for(lambda: self.assertTrue(portal.is_displayed()))

        title = portal.find_element(By.CSS_SELECTOR, ".tt-title").text
        self.assertEqual(title.strip(), "Player")  # PC → "Player"

        description = portal.find_element(By.CSS_SELECTOR, ".tt-description").text
        self.assertEqual(description.strip(), "[Placeholder description]")

    # ------------------------------------------------------------------ #
    # T2 — Mousing well off the card clears the portal.                   #
    # ------------------------------------------------------------------ #

    def test_mouseleave_clears_tooltip_portal(self):
        room = self._make_room_with_role(role="EC")
        self.create_pre_authenticated_session(self.EMAILS[0])
        self.browser.get(f"{self.live_server_url}/gameboard/room/{room.id}/gate/")

        self._open_tray()
        role_img = self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR, "#id_tray_grid .tray-role-card img"
            )
        )
        portal = self.browser.find_element(By.ID, "id_tooltip_portal")

        self._hover(role_img)
        self.wait_for(lambda: self.assertTrue(portal.is_displayed()))
        # Title reflects the EC mapping.
        self.assertEqual(
            portal.find_element(By.CSS_SELECTOR, ".tt-title").text.strip(),
            "Economist",
        )

        self._move_far_away()
        self.wait_for(lambda: self.assertFalse(portal.is_displayed()))


class TraySigCardTooltipTest(FunctionalTest):
    """Phase 2 — sig-card tooltip mirrors #id_fan_fyi_panel functionally:
    Energy/Operation entries cycled via PRV/NXT btns, "1/N" pager, no stage
    block, no Reversal entries. Extended-mouseleave (union incl. PRV/NXT btn
    rects) replaces the fan-stage version's click-to-dismiss handler.
    """

    EMAILS = [
        "ts1@test.io", "ts2@test.io", "ts3@test.io",
        "ts4@test.io", "ts5@test.io", "ts6@test.io",
    ]

    def setUp(self):
        super().setUp()
        self.browser.set_window_size(800, 1200)
        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"}
        )

    def _make_room_with_sig(self):
        """Room w. founder seated, role assigned, & a TarotCard set as sig.

        The seeded card carries 1 energy + 1 operation = 2 FYI entries so the
        PRV/NXT pager has something to cycle through.
        """
        founder, _ = User.objects.get_or_create(email=self.EMAILS[0])
        deck, _ = DeckVariant.objects.get_or_create(
            slug="earthman",
            defaults={"name": "Earthman", "card_count": 106, "is_default": True},
        )
        founder.equipped_deck = deck
        founder.save(update_fields=["equipped_deck"])
        # Stable test fixture — slug + arcana + suit unique per (deck_variant, slug).
        # corner_rank / suit_icon are computed properties on TarotCard;
        # only the underlying fields go in create().
        sig_card = TarotCard.objects.create(
            deck_variant=deck,
            name="The Tester",
            arcana="MAJOR",
            number=98,
            slug="the-tester-sig-tt",
            icon="fa-flask",
            energies=[
                {"type": "TESTLIBIDO", "effect": "First energy effect."},
            ],
            operations=[
                {"type": "TESTOP", "effect": "First operation effect."},
            ],
        )
        room = Room.objects.create(name="Sig Tooltip Room", owner=founder)
        _fill_room_via_orm(room, self.EMAILS)
        room.table_status = Room.ROLE_SELECT
        room.save()
        for slot in room.gate_slots.order_by("slot_number"):
            seat = TableSeat.objects.create(
                room=room, gamer=slot.gamer, slot_number=slot.slot_number,
                role=("PC" if slot.slot_number == 1 else None),
            )
            if slot.slot_number == 1:
                seat.significator = sig_card
                seat.save(update_fields=["significator"])
        return room, sig_card

    def _open_tray(self):
        self.browser.execute_script("Tray.open();")
        self.wait_for(
            lambda: self.assertLess(
                self.browser.execute_script(
                    "return document.getElementById('id_tray_wrap').getBoundingClientRect().left"
                ),
                40,
            )
        )

    def _hover(self, element):
        # Synthetic mouseenter — Selenium's ActionChains animates the pointer
        # from the previous position to the destination. With role-card and
        # sig-card cells side-by-side in the tray grid, an ActionChains hover
        # on the sig stage crosses the role-card's hit area on the way and
        # opens the role tooltip mid-flight, which then occludes the sig stage
        # by the time the pointer lands. Direct dispatch avoids this and still
        # exercises the same listener path (mouseenter → _showSig).
        self.browser.execute_script(
            "arguments[0].dispatchEvent(new MouseEvent('mouseenter',"
            "{bubbles:false, cancelable:false}));",
            element,
        )

    def _move_far_away(self):
        self.browser.execute_script(
            "document.dispatchEvent(new MouseEvent('mousemove',"
            "{bubbles:true, clientX:9999, clientY:9999}));"
        )

    # ------------------------------------------------------------------ #
    # T1 — Hover sig card → portal shows first energy entry               #
    # ------------------------------------------------------------------ #

    def test_hover_sig_card_populates_fyi_portal(self):
        room, _card = self._make_room_with_sig()
        self.create_pre_authenticated_session(self.EMAILS[0])
        self.browser.get(f"{self.live_server_url}/gameboard/room/{room.id}/gate/")

        self._open_tray()
        sig_stage = self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR, "#id_tray_grid .tray-sig-card .sig-stage-card"
            )
        )
        portal = self.browser.find_element(By.ID, "id_tooltip_portal")
        self.assertFalse(portal.is_displayed())

        self._hover(sig_stage)
        self.wait_for(lambda: self.assertTrue(portal.is_displayed()))

        # Portal renders the same .sig-info structure as the fan FYI panel.
        title  = portal.find_element(By.CSS_SELECTOR, ".sig-info-title").text.strip()
        ttype  = portal.find_element(By.CSS_SELECTOR, ".sig-info-type").text.strip()
        effect = portal.find_element(By.CSS_SELECTOR, ".sig-info-effect").text.strip()
        index  = portal.find_element(By.CSS_SELECTOR, ".sig-info-index").text.strip()
        self.assertEqual(title, "Energy")
        self.assertEqual(ttype, "TESTLIBIDO")
        self.assertEqual(effect, "First energy effect.")
        self.assertEqual(index, "1 / 2")  # 1 energy + 1 operation = 2 entries

        # PRV + NXT buttons are present.
        portal.find_element(By.CSS_SELECTOR, ".fyi-prev")
        portal.find_element(By.CSS_SELECTOR, ".fyi-next")

    # ------------------------------------------------------------------ #
    # T2 — NXT cycles to operation entry; PRV cycles back                  #
    # ------------------------------------------------------------------ #

    def test_prv_nxt_cycle_through_entries(self):
        room, _card = self._make_room_with_sig()
        self.create_pre_authenticated_session(self.EMAILS[0])
        self.browser.get(f"{self.live_server_url}/gameboard/room/{room.id}/gate/")

        self._open_tray()
        sig_stage = self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR, "#id_tray_grid .tray-sig-card .sig-stage-card"
            )
        )
        portal = self.browser.find_element(By.ID, "id_tooltip_portal")

        self._hover(sig_stage)
        self.wait_for(lambda: self.assertTrue(portal.is_displayed()))

        nxt = portal.find_element(By.CSS_SELECTOR, ".fyi-next")
        nxt.click()
        self.wait_for(
            lambda: self.assertEqual(
                portal.find_element(By.CSS_SELECTOR, ".sig-info-title").text.strip(),
                "Operation",
            )
        )
        self.assertEqual(
            portal.find_element(By.CSS_SELECTOR, ".sig-info-type").text.strip(),
            "TESTOP",
        )
        self.assertEqual(
            portal.find_element(By.CSS_SELECTOR, ".sig-info-index").text.strip(),
            "2 / 2",
        )

        prv = portal.find_element(By.CSS_SELECTOR, ".fyi-prev")
        prv.click()
        self.wait_for(
            lambda: self.assertEqual(
                portal.find_element(By.CSS_SELECTOR, ".sig-info-title").text.strip(),
                "Energy",
            )
        )

    # ------------------------------------------------------------------ #
    # T3 — Clicking the panel body does NOT dismiss (departure from fan).  #
    # ------------------------------------------------------------------ #

    def test_panel_body_click_does_not_dismiss(self):
        room, _card = self._make_room_with_sig()
        self.create_pre_authenticated_session(self.EMAILS[0])
        self.browser.get(f"{self.live_server_url}/gameboard/room/{room.id}/gate/")

        self._open_tray()
        sig_stage = self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR, "#id_tray_grid .tray-sig-card .sig-stage-card"
            )
        )
        portal = self.browser.find_element(By.ID, "id_tooltip_portal")

        self._hover(sig_stage)
        self.wait_for(lambda: self.assertTrue(portal.is_displayed()))

        # Click on the panel body (not on PRV / NXT) → portal stays active.
        portal.find_element(By.CSS_SELECTOR, ".sig-info-effect").click()
        self.assertTrue(portal.is_displayed())

    # ------------------------------------------------------------------ #
    # T4 — Mouseleave (outside trigger + portal + btn rects) clears.       #
    # ------------------------------------------------------------------ #

    def test_mouseleave_outside_union_clears_portal(self):
        room, _card = self._make_room_with_sig()
        self.create_pre_authenticated_session(self.EMAILS[0])
        self.browser.get(f"{self.live_server_url}/gameboard/room/{room.id}/gate/")

        self._open_tray()
        sig_stage = self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR, "#id_tray_grid .tray-sig-card .sig-stage-card"
            )
        )
        portal = self.browser.find_element(By.ID, "id_tooltip_portal")

        self._hover(sig_stage)
        self.wait_for(lambda: self.assertTrue(portal.is_displayed()))

        self._move_far_away()
        self.wait_for(lambda: self.assertFalse(portal.is_displayed()))
