"""Functional tests for the DRAW SEA overlay — Celtic Cross draw."""

from django.test import tag
from django.urls import reverse
from django.utils import timezone
from selenium.webdriver.common.by import By

from apps.applets.models import Applet
from apps.epic.models import Character, GateSlot, Room, TableSeat, TarotCard, DeckVariant
from apps.lyric.models import User

from .base import ChannelsFunctionalTest


def _make_sky_confirmed_room(live_server_url, user, earthman):
    """Create a SKY_SELECT room with one gamer seated and sig assigned.

    Returns (room, seat). The Character is NOT yet confirmed — call
    _confirm_sky() in the browser to trigger the async transition.
    """
    room = Room.objects.create(
        name="Sea Test Room", table_status=Room.SKY_SELECT, owner=user
    )
    slot = room.gate_slots.get(slot_number=1)
    slot.gamer = user
    slot.status = GateSlot.FILLED
    slot.save()
    room.gate_status = Room.OPEN
    room.save()

    sig_card = TarotCard.objects.filter(deck_variant=earthman, arcana="MAJOR").first()
    seat = TableSeat.objects.create(
        room=room, gamer=user, role="PC", slot_number=1,
        deck_variant=earthman, significator=sig_card,
    )
    return room, seat


@tag("channels")
class PickSeaAsyncTransitionTest(ChannelsFunctionalTest):
    """After sky confirm, the sky overlay closes and the room reloads to the
    table hex w. the DRAW SEA btn visible — the gamer must opt into the sea
    overlay rather than be auto-launched into it."""

    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"}
        )
        from apps.lyric.models import User
        gamer, _ = User.objects.get_or_create(email="founder@test.io")
        earthman, _ = DeckVariant.objects.get_or_create(
            slug="earthman",
            defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True},
        )
        gamer.unlocked_decks.add(earthman)
        gamer.equipped_deck = earthman
        gamer.save(update_fields=["equipped_deck"])

        self.gamer = gamer
        self.room, self.seat = _make_sky_confirmed_room(self.live_server_url, gamer, earthman)
        self.room_url = self.live_server_url + reverse(
            "epic:room", kwargs={"room_id": self.room.id}
        )
        self.sky_save_url = self.live_server_url + reverse(
            "epic:sky_save", kwargs={"room_id": self.room.id}
        )

    def _confirm_sky(self):
        """POST to sky_save with action=confirm from browser JS (bypasses chart form)."""
        # Wait for the room WS connection to be ready before triggering confirm
        self.wait_for(lambda: self.browser.execute_script(
            "return !!(window._roomSocket && window._roomSocket.readyState === 1);"
        ))
        self.browser.execute_script(f"""
            const csrf = (document.cookie.match(/csrftoken=([^;]+)/) || ['',''])[1];
            fetch('{self.sky_save_url}', {{
                method: 'POST',
                credentials: 'same-origin',
                headers: {{'Content-Type': 'application/json', 'X-CSRFToken': csrf}},
                body: JSON.stringify({{
                    birth_dt: '1990-06-15T09:00:00Z',
                    birth_lat: 51.5, birth_lon: -0.1,
                    birth_place: 'London', house_system: 'O',
                    chart_data: {{}}, action: 'confirm',
                }}),
            }});
        """)

    def test_pick_sea_btn_visible_after_sky_confirm(self):
        """Confirming sky lands the room on the hex w. DRAW SEA in place of CAST
        SKY; the sea overlay is NOT auto-opened. (A direct-POST confirm has no
        captured chart on this browser, so _onSkyConfirmed reloads to the server-
        rendered DRAW SEA hex; the felt-save path eases there via the cascade.)"""
        self.create_pre_authenticated_session("founder@test.io")
        self.browser.get(self.room_url)

        # Sky not yet confirmed — CAST SKY is the live (non --out) phase btn.
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sky_btn"))

        self._confirm_sky()

        # DRAW SEA becomes the live phase btn; CAST SKY stays in the phase-stack
        # but goes --out (the buttons cross-fade in one grid cell now, so the
        # stale CAST SKY is hidden via the class, not removed from the DOM).
        self.wait_for(lambda: self.assertNotIn(
            "hex-phase-btn--out",
            self.browser.find_element(By.ID, "id_pick_sea_btn").get_attribute("class"),
        ))
        self.assertIn(
            "hex-phase-btn--out",
            self.browser.find_element(By.ID, "id_pick_sky_btn").get_attribute("class"),
        )

        # Sea overlay is NOT auto-opened — it only appears once the gamer
        # clicks DRAW SEA.
        has_sea_open = self.browser.execute_script(
            "return document.documentElement.classList.contains('sea-open');"
        )
        self.assertFalse(has_sea_open)

    def test_sky_overlay_closed_after_sky_confirm(self):
        """Sky overlay is gone (page reloaded) after sky confirm."""
        self.create_pre_authenticated_session("founder@test.io")
        self.browser.get(self.room_url)
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sky_btn"))

        self._confirm_sky()

        self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sea_btn"))
        sky = self.browser.find_elements(By.ID, "id_sky_overlay")
        self.assertTrue(not sky or not sky[0].is_displayed())

    def test_clicking_pick_sea_btn_opens_sea_overlay(self):
        """The gamer's explicit click on DRAW SEA is what opens the sea overlay."""
        self.create_pre_authenticated_session("founder@test.io")
        self.browser.get(self.room_url)
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sky_btn"))

        self._confirm_sky()
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sea_btn"))

        # On slow CI, the DRAW SEA btn parses into the DOM before the inline
        # `<script>` at the bottom of _sea_overlay.html has bound `openSea` to
        # it; a one-shot click can land before the handler exists. Retry click
        # + assert together via wait_for so the race resolves naturally.
        def _click_and_assert_sea_open():
            btn = self.browser.find_element(By.ID, "id_pick_sea_btn")
            self.browser.execute_script("arguments[0].click()", btn)
            self.assertTrue(self.browser.execute_script(
                "return document.documentElement.classList.contains('sea-open')"
            ))
        self.wait_for(_click_and_assert_sea_open)

        sea_overlay = self.browser.find_element(By.ID, "id_sea_overlay")
        self.assertTrue(sea_overlay.is_displayed())


# ── Helpers for DRAW SEA deal tests ──────────────────────────────────────────

def _seed_earthman_cards(earthman, count=20):
    """Seed enough Middle Arcana cards for the deck piles."""
    suits = ["BRANDS", "GRAILS", "BLADES", "CROWNS"]
    nums  = [11, 12, 13, 14]
    for suit in suits:
        for num in nums:
            TarotCard.objects.get_or_create(
                deck_variant=earthman,
                slug=f"m{num}-{suit.lower()}-em",
                defaults={"arcana": "MIDDLE", "suit": suit, "number": num,
                          "name": f"Card {num} {suit}"},
            )


def _make_sea_ready_room(earthman):
    """Create a SKY_SELECT room with a confirmed Character ready for DRAW SEA.

    Returns (room, gamer, seat, char, room_url).
    """
    gamer, _ = User.objects.get_or_create(email="founder@test.io")
    gamer.unlocked_decks.add(earthman)
    gamer.equipped_deck = earthman
    gamer.save(update_fields=["equipped_deck"])

    room = Room.objects.create(
        name="Sea Deal Room", table_status=Room.SKY_SELECT, owner=gamer
    )
    slot = room.gate_slots.get(slot_number=1)
    slot.gamer = gamer
    slot.status = GateSlot.FILLED
    slot.save()
    room.gate_status = Room.OPEN
    room.save()

    sig_card = TarotCard.objects.filter(deck_variant=earthman, arcana="MAJOR").first()
    seat = TableSeat.objects.create(
        room=room, gamer=gamer, role="PC", slot_number=1,
        deck_variant=earthman, significator=sig_card,
    )
    char = Character.objects.create(
        seat=seat, significator=sig_card, confirmed_at=timezone.now()
    )
    return room, gamer, seat, char


@tag("channels")
class PickSeaDealTest(ChannelsFunctionalTest):
    """DRAW SEA deck stacks, OK btn interaction, card draw, and completion.

    Post-06-07 the felt opens on the spread OPTIONS page; OK'ing the spread
    (`id_sea_confirm_spread`) adds `sea-spread-chosen` → the cross page is
    revealed (the deck stacks live there, `display:none` until then). LOCK HAND
    was dropped: AUTO DRAW + auto-completion on the 6th draw replaced it (no
    discrete lock btn — see the `PickSeaUnifiedFeltTest` ITs for SEED MAP)."""

    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"}
        )
        # Major Arcana sig card
        earthman, _ = DeckVariant.objects.get_or_create(
            slug="earthman",
            defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True},
        )
        TarotCard.objects.get_or_create(
            deck_variant=earthman, slug="the-schizo-em",
            defaults={"arcana": "MAJOR", "number": 1, "name": "The Schizo",
                      "levity_qualifier": "Enlightened", "gravity_qualifier": "Engraven"},
        )
        _seed_earthman_cards(earthman)
        self.room, self.gamer, self.seat, self.char = _make_sea_ready_room(earthman)
        self.room_url = self.live_server_url + reverse(
            "epic:room", kwargs={"room_id": self.room.id}
        )

    def _load_sea_overlay(self):
        """Navigate to room page and open the sea overlay."""
        self.create_pre_authenticated_session("founder@test.io")
        self.browser.get(self.room_url)
        btn = self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sea_btn"))
        self.browser.execute_script("arguments[0].click()", btn)
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_sea_overlay"))

    def _choose_spread(self):
        """OK the default spread → reveal the cross page (the deck stacks).

        The felt opens on the OPTIONS page; the deck stacks live in
        `.sea-cross-col`, which is `display:none` until OK adds
        `sea-spread-chosen` (see `_sky.scss`). Tests that interact with a stack
        must choose a spread first."""
        ok = self.wait_for(
            lambda: self.browser.find_element(By.ID, "id_sea_confirm_spread")
        )
        self.browser.execute_script("arguments[0].click()", ok)
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--levity"
        ).is_displayed())

    # ── Button presence ───────────────────────────────────────────────── #

    def test_deal_btn_absent(self):
        """DEAL btn replaced by the deck stacks + AUTO DRAW."""
        self._load_sea_overlay()
        self.assertEqual(self.browser.find_elements(By.ID, "id_sea_deal"), [])

    def test_del_btn_present(self):
        """DEL btn is always present in the sea overlay."""
        self._load_sea_overlay()
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_sea_del"))

    # ── Deck stacks ───────────────────────────────────────────────────── #

    def test_two_deck_stacks_present(self):
        """Both levity and gravity deck stacks are visible."""
        self._load_sea_overlay()
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--levity"
        ))
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--gravity"
        ))

    def test_clicking_stack_shows_ok_btn(self):
        """Clicking a deck stack reveals its OK btn (spread chosen first — the
        stacks live on the cross page, hidden until OK)."""
        self._load_sea_overlay()
        self._choose_spread()
        stack = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--levity"
        ))
        self.browser.execute_script("arguments[0].click()", stack)
        ok_btn = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--levity .sea-stack-flip"
        ))
        # Choosing the spread scroll-snaps the felt onto two pages; the deck
        # stacks sit on a different page than the post-OK scroll lands on in
        # headless (the rAF scroll-to-cross is dropped, see
        # [[feedback-headless-delayed-scroll-dropped]]), leaving the revealed OK
        # btn off the visible page. Pull it into view so is_displayed reflects
        # the `--active` reveal, not the scroll position.
        self.browser.execute_script(
            "arguments[0].scrollIntoView({block: 'center'})", ok_btn
        )
        self.wait_for(lambda: self.assertTrue(ok_btn.is_displayed()))

    def test_clicking_elsewhere_hides_ok_btn(self):
        """Clicking outside a focused stack dismisses the OK btn."""
        self._load_sea_overlay()
        stack = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--levity"
        ))
        self.browser.execute_script("arguments[0].click()", stack)
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--levity .sea-stack-flip"
        ).is_displayed())
        # Click the sea cards column (not a stack)
        col = self.browser.find_element(By.CSS_SELECTOR, ".sea-cards-col")
        self.browser.execute_script("arguments[0].click()", col)
        self.wait_for(lambda: not any(
            el.is_displayed()
            for el in self.browser.find_elements(By.CSS_SELECTOR, ".sea-stack-flip")
        ))

    # ── Card draw ─────────────────────────────────────────────────────── #

    def test_ok_click_fills_cover_position(self):
        """First OK click places a card in the Cover position."""
        self._load_sea_overlay()
        stack = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--levity"
        ))
        self.browser.execute_script("arguments[0].click()", stack)
        ok_btn = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--levity .sea-stack-flip"
        ))
        self.browser.execute_script("arguments[0].click()", ok_btn)
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-pos-cover .sea-card-slot--filled"
        ))

    def test_six_draws_complete_and_lock_the_deck_stacks(self):
        """Drawing all 6 cards auto-completes the hand (no discrete LOCK HAND):
        the deck-stack FLIP btns get `.btn-disabled` the instant the 6th card
        lands — the synchronous completion marker, fired before the 3s-delayed
        felt cascade. (SEED MAP / DRAW SEA --out is IT-covered.)"""
        self._load_sea_overlay()
        self._choose_spread()

        for _ in range(6):
            stack = self.wait_for(lambda: self.browser.find_element(
                By.CSS_SELECTOR, ".sea-deck-stack--levity"
            ))
            self.browser.execute_script("arguments[0].click()", stack)
            ok_btn = self.wait_for(lambda: self.browser.find_element(
                By.CSS_SELECTOR, ".sea-deck-stack--levity .sea-stack-flip"
            ))
            self.browser.execute_script("arguments[0].click()", ok_btn)

        # Assert on the class (not is_displayed) — it survives the element going
        # display:none when the felt later cascades out.
        self.wait_for(lambda: self.assertIn(
            "btn-disabled",
            self.browser.find_element(
                By.CSS_SELECTOR, ".sea-deck-stack--levity .sea-stack-flip"
            ).get_attribute("class"),
        ))

    def test_del_clears_drawn_cards(self):
        """DEL btn clears all drawn cards and resets positions to empty."""
        self._load_sea_overlay()
        # Draw one card
        stack = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--levity"
        ))
        self.browser.execute_script("arguments[0].click()", stack)
        ok_btn = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-deck-stack--levity .sea-stack-flip"
        ))
        self.browser.execute_script("arguments[0].click()", ok_btn)
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".sea-pos-cover .sea-card-slot--filled"
        ))
        # DEL clears it
        del_btn = self.browser.find_element(By.ID, "id_sea_del")
        self.browser.execute_script("arguments[0].click()", del_btn)
        self.wait_for(lambda: not self.browser.find_elements(
            By.CSS_SELECTOR, ".sea-card-slot--filled"
        ))
