"""
Functional tests: game-room invite flow via BillPost.

BillPost is a new billboard.BillPost model (kind=INVITE for this sprint;
more kinds to follow in later feature sprints).  It is the first of a series
of Post-types that will grow through coming sprints.  The existing
dashboard.Post model will be renamed (to Message or similar) when the WS
messaging sprint lands; this new system's naming takes precedence.

Sprint 3 (GameInviteNotificationTest) — @two-browser:
  Founder sends an email invite from the room page.  invite_gamer() creates a
  BillPost(kind=INVITE) for the invitee.  Invitee sees "INVITE: <room>" as a
  link in the My Posts applet on the billboard page.

Sprint 4 (GameInviteBillPostTest):
  Clicking the invite post navigates to /billboard/post/<pk>/.  The page
  renders a _billpost_invite.html partial with room info.  OK and BYE
  (.btn-confirm / .btn-abandon) appear next to the title.
  BYE marks the BillPost dismissed; the post is no longer listed in My Posts.
  OK shows a Log-Out-style confirm guard ("Join game?  OK / NVM") if the
  invitee has a valid (non-contributing) deck equipped.

Sprint 5 (GameInviteDeckValidationTest):
  If the invitee has no deck equipped, or their equipped deck is already
  assigned to an active seat, the OK button is immediately btn-disabled
  (no guard shown) and a tooltip explains the situation.
  When a valid deck is equipped and the invitee confirms, they are redirected
  to the room and their deck is assigned to a seat.
"""
import os
import time

from django.test import tag
from selenium import webdriver
from selenium.webdriver.common.by import By

import unittest

from apps.applets.models import Applet
from apps.epic.models import DeckVariant, GateSlot, Room, RoomInvite, TableSeat

try:
    from apps.billboard.models import BillPost
    _BILLPOST_READY = True
except ImportError:
    BillPost = None
    _BILLPOST_READY = False
from apps.lyric.models import User
from functional_tests.base import FunctionalTest
from functional_tests.room_page import _fill_room_via_orm

FOUNDER_EMAIL  = "founder@test.io"
INVITEE_EMAIL  = "invitee@test.io"


def _applets():
    for slug, name, ctx in [
        ("my-posts",             "My Posts",  "billboard"),
        ("my-scrolls",           "My Scrolls","billboard"),
    ]:
        Applet.objects.get_or_create(slug=slug, defaults={"name": name, "context": ctx})


def _make_open_room(name="Wildfire"):
    founder, _ = User.objects.get_or_create(email=FOUNDER_EMAIL)
    room = Room.objects.create(name=name, owner=founder)
    _fill_room_via_orm(room, [FOUNDER_EMAIL,
                               "b@test.io", "c@test.io", "d@test.io", "e@test.io", "f@test.io"])
    return room, founder


def _make_second_browser():
    opts = webdriver.FirefoxOptions()
    if os.environ.get("HEADLESS"):
        opts.add_argument("--headless")
    b = webdriver.Firefox(options=opts)
    b.set_window_size(800, 1200)
    return b


# ── Sprint 3 ─────────────────────────────────────────────────────────────────

@unittest.skipUnless(_BILLPOST_READY, "BillPost not yet implemented")
@tag("two-browser")
class GameInviteNotificationTest(FunctionalTest):
    """Sprint 3: founder sends invite → INVITE: BillPost appears in invitee's My Posts."""

    def setUp(self):
        super().setUp()
        self.browser.set_window_size(800, 1200)
        self.second_browser = _make_second_browser()
        _applets()
        User.objects.get_or_create(email=INVITEE_EMAIL)

    def tearDown(self):
        self.second_browser.quit()
        super().tearDown()

    def test_invite_creates_bill_post_in_my_posts(self):
        """Founder sends email invite; invitee sees 'INVITE: Wildfire' link in My Posts."""
        room, founder = _make_open_room()

        # Founder: log in, navigate to room, send invite
        founder_session = self.create_pre_authenticated_session(FOUNDER_EMAIL)
        self.browser.get(self.live_server_url)
        self.browser.add_cookie({"name": "sessionid", "value": founder_session})
        self.browser.get(self.live_server_url + f"/gameboard/room/{room.pk}/")

        invite_input = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, "input[name='invitee_email']")
        )
        invite_input.send_keys(INVITEE_EMAIL)
        self.browser.find_element(By.CSS_SELECTOR, "#id_invite_btn").click()

        # A BillPost(kind=INVITE) is created for the invitee
        self.wait_for(lambda: self.assertTrue(
            BillPost.objects.filter(
                kind=BillPost.INVITE,
                recipient__email=INVITEE_EMAIL,
                room=room,
                dismissed=False,
            ).exists(),
            "BillPost(kind=INVITE) was not created for the invitee",
        ))

        # Invitee: log in to billboard, see "INVITE: Wildfire" in My Posts applet
        invitee = User.objects.get(email=INVITEE_EMAIL)
        invitee_session = self.create_pre_authenticated_session(INVITEE_EMAIL)
        self.second_browser.get(self.live_server_url)
        self.second_browser.add_cookie({"name": "sessionid", "value": invitee_session})
        self.second_browser.get(self.live_server_url + "/billboard/")

        my_posts = self.wait_for(
            lambda: self.second_browser.find_element(By.CSS_SELECTOR, "#id_applet_my_posts")
        )
        invite_link = self.wait_for(
            lambda: my_posts.find_element(By.PARTIAL_LINK_TEXT, "INVITE:")
        )
        self.assertIn("WILDFIRE", invite_link.text.upper())

        # Link goes to /billboard/post/<pk>/
        post = BillPost.objects.get(kind=BillPost.INVITE, recipient__email=INVITEE_EMAIL)
        self.assertIn(f"/billboard/post/{post.pk}/", invite_link.get_attribute("href"))


# ── Sprint 4 ─────────────────────────────────────────────────────────────────

@unittest.skipUnless(_BILLPOST_READY, "BillPost not yet implemented")
class GameInviteBillPostTest(FunctionalTest):
    """Sprint 4: BillPost invite page — _billpost_invite.html partial, OK/BYE, BYE dismisses."""

    def setUp(self):
        super().setUp()
        self.browser.set_window_size(800, 1200)
        _applets()

    def _make_invite_post(self, equip_deck=False):
        room, founder = _make_open_room()
        invitee, _ = User.objects.get_or_create(email=INVITEE_EMAIL)
        invite = RoomInvite.objects.create(
            room=room, inviter=founder, invitee_email=INVITEE_EMAIL,
            status=RoomInvite.PENDING,
        )
        post = BillPost.objects.create(
            kind=BillPost.INVITE, recipient=invitee, room=room, invite=invite,
        )
        if equip_deck:
            earthman = DeckVariant.objects.get(slug="earthman")
            invitee.equipped_deck = earthman
            invitee.unlocked_decks.add(earthman)
            invitee.save(update_fields=["equipped_deck"])
        return invitee, post, room

    def test_billpost_page_shows_invite_partial(self):
        """The BillPost page at /billboard/post/<pk>/ renders the invite partial with room info."""
        invitee, post, room = self._make_invite_post()
        session_key = self.create_pre_authenticated_session(INVITEE_EMAIL)
        self.browser.get(self.live_server_url)
        self.browser.add_cookie({"name": "sessionid", "value": session_key})
        self.browser.get(self.live_server_url + f"/billboard/post/{post.pk}/")

        # Page title / heading shows "INVITE: Wildfire"
        heading = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".billpost-title")
        )
        self.assertIn("INVITE", heading.text.upper())
        self.assertIn(room.name.upper(), heading.text.upper())

        # Invite partial is present
        self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".billpost-invite")
        )

        # OK and BYE buttons are present
        self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, ".btn-confirm"))
        self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, ".btn-abandon"))

    def test_bye_dismisses_invite_and_removes_from_my_posts(self):
        """Clicking BYE marks the BillPost dismissed and it no longer appears in My Posts."""
        invitee, post, room = self._make_invite_post()
        session_key = self.create_pre_authenticated_session(INVITEE_EMAIL)
        self.browser.get(self.live_server_url)
        self.browser.add_cookie({"name": "sessionid", "value": session_key})
        self.browser.get(self.live_server_url + f"/billboard/post/{post.pk}/")

        bye_btn = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".btn-abandon")
        )
        bye_btn.click()

        # BillPost is now dismissed in DB
        post.refresh_from_db()
        self.assertTrue(post.dismissed)

        # Redirected to billboard; post no longer in My Posts
        self.wait_for(lambda: self.assertIn("/billboard/", self.browser.current_url))
        my_posts = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_applet_my_posts")
        )
        invite_links = my_posts.find_elements(By.PARTIAL_LINK_TEXT, "INVITE:")
        self.assertEqual(len(invite_links), 0, "Dismissed invite should not appear in My Posts")

    def test_ok_with_valid_deck_shows_confirm_guard(self):
        """With a valid deck equipped, OK shows a Log-Out-style confirm guard."""
        invitee, post, room = self._make_invite_post(equip_deck=True)
        session_key = self.create_pre_authenticated_session(INVITEE_EMAIL)
        self.browser.get(self.live_server_url)
        self.browser.add_cookie({"name": "sessionid", "value": session_key})
        self.browser.get(self.live_server_url + f"/billboard/post/{post.pk}/")

        ok_btn = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".btn-confirm")
        )
        self.assertNotIn("btn-disabled", ok_btn.get_attribute("class"))
        ok_btn.click()

        # Confirm guard appears ("Join game?" with OK / NVM)
        guard = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".join-guard")
        )
        self.assertIn("JOIN", guard.text.upper())
        self.wait_for(lambda: guard.find_element(By.CSS_SELECTOR, ".btn-confirm"))
        self.wait_for(lambda: guard.find_element(By.CSS_SELECTOR, ".btn-nvm"))


# ── Sprint 5 ─────────────────────────────────────────────────────────────────

@unittest.skipUnless(_BILLPOST_READY, "BillPost not yet implemented")
class GameInviteDeckValidationTest(FunctionalTest):
    """Sprint 5: OK deck validation — disabled without valid deck; on join, deck locked."""

    def setUp(self):
        super().setUp()
        self.browser.set_window_size(800, 1200)
        _applets()
        for slug, name, ctx in [
            ("game-kit", "Game Kit",  "gameboard"),
            ("gk-decks", "Card Decks","game-kit"),
        ]:
            Applet.objects.get_or_create(slug=slug, defaults={"name": name, "context": ctx})

    def _make_invite_post(self, equip_deck=False, deck_in_use=False):
        room, founder = _make_open_room()
        invitee, _ = User.objects.get_or_create(email=INVITEE_EMAIL)
        invite = RoomInvite.objects.create(
            room=room, inviter=founder, invitee_email=INVITEE_EMAIL,
            status=RoomInvite.PENDING,
        )
        post = BillPost.objects.create(
            kind=BillPost.INVITE, recipient=invitee, room=room, invite=invite,
        )
        earthman = DeckVariant.objects.get(slug="earthman")
        if equip_deck or deck_in_use:
            invitee.equipped_deck = earthman
            invitee.unlocked_decks.add(earthman)
            invitee.save(update_fields=["equipped_deck"])
        if deck_in_use:
            # Simulate the deck already being assigned to another active seat
            other_room = Room.objects.create(name="Other Game", owner=invitee)
            TableSeat.objects.create(
                gamer=invitee, room=other_room, role="PC", deck_variant=earthman,
            )
        return invitee, post, room, earthman

    def test_ok_immediately_disabled_without_equipped_deck(self):
        """OK is btn-disabled on load when invitee has no deck equipped."""
        invitee, post, room, _ = self._make_invite_post(equip_deck=False)
        session_key = self.create_pre_authenticated_session(INVITEE_EMAIL)
        self.browser.get(self.live_server_url)
        self.browser.add_cookie({"name": "sessionid", "value": session_key})
        self.browser.get(self.live_server_url + f"/billboard/post/{post.pk}/")

        ok_btn = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".btn-confirm")
        )
        self.assertIn("btn-disabled", ok_btn.get_attribute("class"),
                      "OK should be disabled when invitee has no deck equipped")

        # Tooltip explains the situation
        ok_btn.click()
        tooltip = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".invite-no-deck-tooltip")
        )
        self.assertTrue(tooltip.is_displayed())

    def test_ok_immediately_disabled_when_deck_in_use(self):
        """OK is btn-disabled when invitee's equipped deck is already assigned to another game."""
        invitee, post, room, _ = self._make_invite_post(deck_in_use=True)
        session_key = self.create_pre_authenticated_session(INVITEE_EMAIL)
        self.browser.get(self.live_server_url)
        self.browser.add_cookie({"name": "sessionid", "value": session_key})
        self.browser.get(self.live_server_url + f"/billboard/post/{post.pk}/")

        ok_btn = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".btn-confirm")
        )
        self.assertIn("btn-disabled", ok_btn.get_attribute("class"),
                      "OK should be disabled when invitee's deck is already in use")

    def test_confirming_join_assigns_deck_to_seat_and_locks_game_kit(self):
        """After confirming join, invitee's deck is assigned to their new seat and
        the Game Kit DON button for that deck becomes btn-disabled."""
        invitee, post, room, earthman = self._make_invite_post(equip_deck=True)
        session_key = self.create_pre_authenticated_session(INVITEE_EMAIL)
        self.browser.get(self.live_server_url)
        self.browser.add_cookie({"name": "sessionid", "value": session_key})
        self.browser.get(self.live_server_url + f"/billboard/post/{post.pk}/")

        # OK → guard → confirm
        ok_btn = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".btn-confirm")
        )
        ok_btn.click()
        guard_ok = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".join-guard .btn-confirm")
        )
        guard_ok.click()

        # Redirected into the room
        self.wait_for(
            lambda: self.assertIn(f"/gameboard/room/{room.pk}/", self.browser.current_url)
        )

        # Invitee's seat has their deck assigned
        self.wait_for(lambda: self.assertTrue(
            TableSeat.objects.filter(
                gamer=invitee, room=room, deck_variant=earthman,
            ).exists(),
            "TableSeat.deck_variant was not set on join",
        ))

        # Game Kit reflects in-use state
        self.browser.get(self.live_server_url + "/gameboard/game-kit/")
        self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_kit_card_deck")
        ).click()
        self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_kit_earthman_deck")
        ).click()
        don_btn = self.wait_for(
            lambda: self.browser.find_element(
                By.CSS_SELECTOR, "#id_kit_earthman_deck .btn-equip"
            )
        )
        self.assertIn("btn-disabled", don_btn.get_attribute("class"))
