"""Position-circle tooltip FTs — RED spec authored 2026-06-01, implemented
tomorrow ([[sprint-position-circle-tooltips]]).

The numbered gate-position circles (1–6) gain rich hover tooltips mirroring
the My Buds bud tooltip, on EVERY surface they appear: the initial gatekeeper,
above the table hex during role/sig select, AND the new GATE VIEW gate-view
(`room_gate.html`), which today renders no circles at all.

Per-circle tooltip content:
  • `.tt-title`       = the occupant's @handle (NO email field)
  • `.tt-description` = the occupant's active title
  • top-right `.tt-sign` stack = the occupant's SEAT significator (rank + suit
    icon — `TableSeat.significator`, per user-spec 2026-06-01), pinned like
    `.tt-price`
  • the bud's shoptalk (`BudshipNote.shoptalk`) when the occupant is a bud
  • the number of tokens deposited (CARTE `slots_claimed`, else 1)
  • `.tt-expiry` of the deposit (`GateSlot.cost_current_until`)

State class on the circle:
  • `.tt-pos-empty`            — empty slot
  • `.tt-pos-gamer`            — another gamer
  • `.tt-pos-gamer.tt-pos-bud` — another gamer who is also a bud (+ shoptalk)
  • `.tt-pos-me-current`       — the viewer's own currently-occupied position
  • `.tt-pos-me-also`          — the viewer's own position they don't occupy now
                                 (CARTE multi-seat). Also carries a `?seat=<n>`
                                 switch href to load that seat's view, so a
                                 CARTE gamer can preview pos-4's ROLE view
                                 (`.fa-ban` atop the deck) or SAVE SIG per seat
                                 during Sig Select.

Written RED — the feature lands tomorrow. FT bucket: game_room.

Both classes are `@skip`-ped so this red spec rides into the repo WITHOUT
breaking CI (the FT stage runs `functional_tests`). Tomorrow's implementation
removes the skip per-method as each behavior goes green.
"""
from unittest import skip

from django.utils import timezone
from selenium.webdriver.common.by import By

from .base import FunctionalTest
from .room_page import _assign_all_roles, _equip_earthman_deck, _fill_room_via_orm
from apps.billboard.models import BudshipNote
from apps.epic.models import GateSlot, Room, TableSeat, TarotCard
from apps.lyric.models import Token, User


_RED = ("RED spec — position-circle tooltips land 2026-06-02 "
        "([[project-position-circle-tooltips]]); remove the skip per-method "
        "as each behavior goes green.")


def _gate_view_url(self, room):
    return self.live_server_url + f"/gameboard/room/{room.id}/gate/view/"


class PositionTooltipTest(FunctionalTest):
    """Tooltip CONTENT + state classes on the gate-position circles, exercised
    on the new GATE VIEW gate-view (the clearest red surface — it renders no
    circles today)."""

    def setUp(self):
        super().setUp()
        # Viewer (slot 1) — give a username so @handle is stable, not email-derived.
        self.viewer = User.objects.create(email="disco@test.io", username="disco")
        _equip_earthman_deck(self.viewer)
        self.room = Room.objects.create(name="Whataburgher", owner=self.viewer)
        self.gamers = _fill_room_via_orm(
            self.room,
            ["disco@test.io", "amigo@test.io", "bud@test.io",
             "pal@test.io", "dude@test.io", "bro@test.io"],
        )
        # Every occupant needs a stable @handle (not email-derived) so the
        # tooltip title reads "@amigo", never "amigo@test.io" — the spec
        # forbids leaking the email into the title.
        for g in self.gamers:
            if not g.username:
                g.username = g.email.split("@")[0]
                g.save(update_fields=["username"])
        # Stamp filled_at so cost_current_until (the .tt-expiry) is real.
        self.room.gate_slots.filter(status=GateSlot.FILLED).update(
            filled_at=timezone.now(), debited_token_type=Token.FREE,
        )
        self.room.table_status = Room.ROLE_SELECT  # gate-view reachable mid-game
        self.room.gate_status = Room.OPEN
        self.room.save()

    def _open_gate_view(self):
        self.create_pre_authenticated_session("disco@test.io")
        self.browser.get(_gate_view_url(self, self.room))
        return self.wait_for(
            lambda: self.browser.find_elements(By.CSS_SELECTOR, ".gate-slot")
        )

    def test_gate_view_renders_six_position_circles(self):
        # room_gate.html renders NO circles today — this is the headline red.
        circles = self._open_gate_view()
        self.assertEqual(len(circles), 6)

    def test_own_occupied_seat_is_pos_me_current(self):
        self._open_gate_view()
        me = self.browser.find_element(By.CSS_SELECTOR, ".gate-slot[data-slot='1']")
        self.assertIn("tt-pos-me-current", me.get_attribute("class"))

    def test_other_gamer_circle_is_pos_gamer_with_handle_title_no_email(self):
        self._open_gate_view()
        other = self.browser.find_element(By.CSS_SELECTOR, ".gate-slot[data-slot='2']")
        cls = other.get_attribute("class")
        self.assertIn("tt-pos-gamer", cls)
        self.assertNotIn("tt-pos-me", cls)
        # Tooltip data rides on the circle (the My Buds data-tt-* pattern).
        self.assertIn("@", other.get_attribute("data-tt-title"))      # @handle
        self.assertTrue(other.get_attribute("data-tt-description"))    # title
        # No email anywhere in the tooltip payload (user-spec).
        self.assertNotIn("@test.io", other.get_attribute("data-tt-title"))
        self.assertIsNone(other.get_attribute("data-tt-email"))

    def test_bud_occupant_circle_is_pos_bud_and_carries_shoptalk(self):
        # Make slot-2's occupant a bud of the viewer, with shoptalk.
        amigo = self.gamers[1]
        self.viewer.buds.add(amigo)
        BudshipNote.objects.create(
            user=self.viewer, bud=amigo, shoptalk="met at the deli",
        )
        self._open_gate_view()
        circle = self.browser.find_element(By.CSS_SELECTOR, ".gate-slot[data-slot='2']")
        cls = circle.get_attribute("class")
        self.assertIn("tt-pos-gamer", cls)
        self.assertIn("tt-pos-bud", cls)
        self.assertEqual(circle.get_attribute("data-tt-shoptalk"), "met at the deli")

    def test_deposit_count_and_expiry_present(self):
        self._open_gate_view()
        circle = self.browser.find_element(By.CSS_SELECTOR, ".gate-slot[data-slot='2']")
        # 1 token deposited (non-CARTE) + a real expiry (cost_current_until).
        self.assertEqual(circle.get_attribute("data-tt-tokens"), "1")
        self.assertTrue(circle.get_attribute("data-tt-expiry"))

    def test_seat_significator_shows_in_sign_stack(self):
        # Assign a significator to seat 2 and assert its rank rides the tooltip.
        _assign_all_roles(self.room)  # creates TableSeats + advances to SIG_SELECT
        seat2 = TableSeat.objects.get(room=self.room, slot_number=2)
        sig = TarotCard.objects.filter(
            deck_variant=self.viewer.equipped_deck, arcana="MIDDLE",
        ).first()
        seat2.significator = sig
        seat2.save(update_fields=["significator"])
        self.room.table_status = Room.ROLE_SELECT  # gate-view circles visible
        self.room.save()
        self._open_gate_view()
        circle = self.browser.find_element(By.CSS_SELECTOR, ".gate-slot[data-slot='2']")
        self.assertEqual(
            circle.get_attribute("data-tt-sign-rank"), str(sig.corner_rank))

    def test_hover_populates_position_tooltip_portal(self):
        # The portal mirrors the My Buds portal: hover a circle → #id_position_
        # tooltip_portal fills + shows the occupant's @handle, NOT their email.
        self._open_gate_view()
        circle = self.browser.find_element(By.CSS_SELECTOR, ".gate-slot[data-slot='2']")
        self.browser.execute_script(
            "arguments[0].dispatchEvent(new MouseEvent('mouseenter', {bubbles:true}))",
            circle,
        )
        portal = self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_position_tooltip_portal")
        )
        self.assertIn("active", portal.get_attribute("class"))
        title = portal.find_element(By.CSS_SELECTOR, ".tt-title").text
        self.assertTrue(title.startswith("@"))
        self.assertNotIn("@test.io", portal.text)


class CarteSeatSwitchTest(FunctionalTest):
    """A CARTE gamer occupies multiple positions. Their non-current owned
    circles read `.tt-pos-me-also` and carry a `?seat=<n>` switch href that
    loads that seat's view — to preview pos-N's ROLE-select state (the `.fa-ban`
    atop the deck) or to SAVE SIG per seat during Sig Select."""

    def setUp(self):
        super().setUp()
        self.viewer = User.objects.create(email="disco@test.io", username="disco")
        _equip_earthman_deck(self.viewer)
        self.room = Room.objects.create(name="Carte Room", owner=self.viewer)
        # CARTE: the viewer owns ALL six slots.
        _fill_room_via_orm(
            self.room, ["disco@test.io"] * 6,  # get_or_create → same viewer in all
        )
        self.room.gate_slots.update(
            gamer=self.viewer, status=GateSlot.FILLED,
            filled_at=timezone.now(), debited_token_type=Token.CARTE,
        )
        carte = Token.objects.create(
            user=self.viewer, token_type=Token.CARTE,
            current_room=self.room, slots_claimed=6,
        )
        self.carte = carte
        self.room.gate_status = Room.OPEN
        self.room.save()

    def test_own_other_seat_is_me_also_with_switch_href(self):
        self.create_pre_authenticated_session("disco@test.io")
        # During ROLE_SELECT the viewer "acts as" their lowest seat; the others
        # are their own seats they don't currently occupy → me-also + switch.
        _assign_all_roles(self.room)
        self.room.table_status = Room.ROLE_SELECT
        # un-assign roles so the card-stack/.fa-ban preview is live
        self.room.table_seats.update(role=None, role_revealed=False)
        self.room.save()
        self.browser.get(self.live_server_url + f"/gameboard/room/{self.room.id}/")
        also = self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR, ".gate-slot[data-slot='4'].tt-pos-me-also")
        )
        href = (also.get_attribute("href")
                or also.find_element(By.CSS_SELECTOR, "a").get_attribute("href"))
        self.assertIn("seat=4", href)

    def test_tokens_deposited_reflects_carte_slots_claimed(self):
        self.create_pre_authenticated_session("disco@test.io")
        self.room.table_status = Room.ROLE_SELECT
        self.room.save()
        self.browser.get(self.live_server_url + f"/gameboard/room/{self.room.id}/gate/view/")
        circle = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-slot[data-slot='1']")
        )
        self.assertEqual(circle.get_attribute("data-tt-tokens"), "6")

    def test_switching_seat_loads_that_seats_role_view(self):
        # Clicking the me-also seat-4 circle loads ?seat=4 and the card-stack
        # reflects seat 4 (a non-active seat → ineligible / .fa-ban atop deck).
        self.create_pre_authenticated_session("disco@test.io")
        _assign_all_roles(self.room)
        self.room.table_status = Room.ROLE_SELECT
        self.room.table_seats.update(role=None, role_revealed=False)
        self.room.save()
        self.browser.get(
            self.live_server_url + f"/gameboard/room/{self.room.id}/?seat=4")
        self.wait_for(
            lambda: self.assertIn(
                "seat=4", self.browser.current_url))
        # Seat 4 is not the active turn → card-stack ineligible w/ the fa-ban.
        stack = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".card-stack"))
        self.assertEqual(stack.get_attribute("data-active-slot"), "4")
        stack.find_element(By.CSS_SELECTOR, ".fa-ban")

    def test_carte_saves_a_significator_per_seat(self):
        # Sig Select: the viewer reserves a sig on PC (seat 1) — a solo-owned
        # polarity group commits it to THAT seat immediately (no 3-gamer
        # countdown). Switching to seat 2 shows its own deck, proving per-seat
        # (not per-gamer) sig.
        self.create_pre_authenticated_session("disco@test.io")
        _assign_all_roles(self.room)   # roles + advance to SIG_SELECT
        self.room.refresh_from_db()
        self.assertEqual(self.room.table_status, Room.SIG_SELECT)
        # Seat 1 (PC) — reserve its sig via the in-card OK button.
        self.browser.get(
            self.live_server_url + f"/gameboard/room/{self.room.id}/?seat=1")
        card1 = self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR, "#id_sig_deck [data-card-id]"))
        ok = card1.find_element(By.CSS_SELECTOR, ".sig-ok-btn")
        self.browser.execute_script("arguments[0].click()", ok)
        self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".sig-card.sig-reserved"))
        # Switch to seat 2 — its sig pick is independent of seat 1's.
        self.browser.get(
            self.live_server_url + f"/gameboard/room/{self.room.id}/?seat=2")
        deck2 = self.wait_for(
            lambda: self.browser.find_elements(
                By.CSS_SELECTOR, "#id_sig_deck [data-card-id]"))
        self.assertTrue(deck2)
        # Each seat ends up with its own significator (per-seat, not shared).
        seat1 = TableSeat.objects.get(room=self.room, slot_number=1)
        seat2 = TableSeat.objects.get(room=self.room, slot_number=2)
        self.assertIsNotNone(seat1.significator_id)
        self.assertNotEqual(seat1.significator_id, seat2.significator_id)
