"""FT for the per-bud landing page at /billboard/buds/<bud_id>/.

bud.html is the destination of the @mailman-invite cascade (see
test_bill_mailman_invite_post.py) AND the click target of the
`@<handle>` anchor on /billboard/my-buds/ (see test_bill_my_buds_tooltip.py).
It's the singular counterpart of my_buds.html in the same way post.html
is to my_posts.html.

Spec recap:
- Page renders all the data the tooltip surfaces (title, description, email,
  shoptalk, milestone) PLUS a 160-char `<textarea>` so the user can edit
  the shoptalk in place. Save persists to a per-relationship BudshipNote.
- 4-button apparatus from my_sea.html: kit / bud / gear / burger.
- The bud `#id_bud_btn` opens but its OK button is disabled (.btn-disabled + ×).
- Gear menu = NVM (→ my_buds) + DEL (→ shared guard portal "Delete this bud?";
  DEL deletes the M2M edge, NVM dismisses the portal).
- Burger fan: 5 sub-btns default to inactive (.fa-ban icon, no .active class).
- Invite-cascade: if the bud has a PENDING SeaInvite where invitee=me,
    • #id_burger_btn carries .glow-handoff (machine cascade from my_sea.html);
    • #id_sea_btn in the fan is .active;
    • clicking the glowing #id_sea_btn navigates to the bud's my-sea page
      AND clears .glow-handoff but PRESERVES .active.
- Visiting bud.html for an inviter who isn't yet in my buds auto-adds them.
"""
from selenium.webdriver.common.by import By

from apps.gameboard.models import SeaInvite
from apps.lyric.models import User

from .base import FunctionalTest


class BudPageRenderTest(FunctionalTest):
    def setUp(self):
        super().setUp()
        self.gamer = User.objects.create(email="me@test.io", username="me")
        self.alice = User.objects.create(email="alice@test.io", username="alice")
        self.gamer.buds.add(self.alice)
        self.create_pre_authenticated_session("me@test.io")

    def test_page_header_reads_at_handle_the_title(self):
        """Same `@alice the Earthman` framing as the my_buds row, hoisted
        into the page header so the user sees who they're on the page of."""
        self.browser.get(
            self.live_server_url + f"/billboard/buds/{self.alice.id}/"
        )
        header = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".bud-page-header"
        ))
        self.assertIn("@alice the Earthman", header.text)
        self.assertIn("alice@test.io", header.text)

    def test_shoptalk_textarea_renders_with_160_char_maxlength(self):
        textarea = self.wait_for(lambda: self._open_and_find_textarea())
        self.assertEqual(textarea.get_attribute("maxlength"), "160")
        self.assertEqual(textarea.get_attribute("value") or "", "")

    def _open_and_find_textarea(self):
        self.browser.get(
            self.live_server_url + f"/billboard/buds/{self.alice.id}/"
        )
        return self.browser.find_element(By.ID, "id_shoptalk")


class BudPageFourButtonApparatusTest(FunctionalTest):
    def setUp(self):
        super().setUp()
        self.gamer = User.objects.create(email="me@test.io", username="me")
        self.alice = User.objects.create(email="alice@test.io", username="alice")
        self.gamer.buds.add(self.alice)
        self.create_pre_authenticated_session("me@test.io")
        self.browser.get(
            self.live_server_url + f"/billboard/buds/{self.alice.id}/"
        )

    def test_all_four_apparatus_buttons_present(self):
        """kit / bud / gear / burger — same 4-btn slate as my_sea.html.
        kit_btn is included via base.html so we don't need to render it
        specially on bud.html."""
        for btn_id in ("id_kit_btn", "id_bud_btn", "id_burger_btn"):
            self.wait_for(lambda bid=btn_id: self.browser.find_element(By.ID, bid))
        # Gear btn is rendered via the shared _gear.html partial — class-
        # only, not an id. Disambiguate by data-menu-target.
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".gear-btn[data-menu-target='id_bud_menu']"
        ))

    def test_bud_btn_panel_ok_is_disabled_and_shows_times(self):
        """Per-spec stub: the bud-on-bud OK btn is intentionally disabled
        for now (.btn-disabled + `×`). Wired later when the bud-of-bud
        flow exists. Slap-on stub avoids removing the apparatus."""
        btn = self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_btn"))
        btn.click()
        ok = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, "#id_bud_panel .btn"
        ))
        self.assertIn("btn-disabled", ok.get_attribute("class") or "")
        self.assertEqual(ok.text.strip(), "×")

    def test_burger_fan_all_five_subbtns_inactive_by_default(self):
        """Fresh load (no pending SeaInvite to me) → every sub-btn renders
        WITHOUT .active. The visible icon for each sub-btn is .fa-ban (the
        `--off` half); the `--on` icon stays hidden by CSS until .active."""
        burger = self.browser.find_element(By.ID, "id_burger_btn")
        burger.click()
        self.wait_for(lambda: self.assertEqual(
            self.browser.find_element(By.ID, "id_burger_fan").get_attribute("aria-hidden"),
            "false",
        ))
        fan_btns = self.browser.find_elements(By.CSS_SELECTOR, "#id_burger_fan .burger-fan-btn")
        self.assertEqual(len(fan_btns), 5)
        for b in fan_btns:
            cls = b.get_attribute("class") or ""
            self.assertNotIn("active", cls.split())


class BudPageGearMenuTest(FunctionalTest):
    def setUp(self):
        super().setUp()
        self.gamer = User.objects.create(email="me@test.io", username="me")
        self.alice = User.objects.create(email="alice@test.io", username="alice")
        self.gamer.buds.add(self.alice)
        self.create_pre_authenticated_session("me@test.io")
        self.browser.get(
            self.live_server_url + f"/billboard/buds/{self.alice.id}/"
        )

    def test_gear_nvm_navigates_back_to_my_buds(self):
        self.browser.find_element(
            By.CSS_SELECTOR, ".gear-btn[data-menu-target='id_bud_menu']"
        ).click()
        nvm = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, "#id_bud_menu .btn-cancel"
        ))
        self.assertEqual(nvm.text.strip(), "NVM")
        nvm.click()
        self.wait_for(lambda: self.assertIn(
            "/billboard/my-buds/", self.browser.current_url
        ))

    def test_gear_del_opens_guard_portal_with_delete_this_bud_message(self):
        self.browser.find_element(
            By.CSS_SELECTOR, ".gear-btn[data-menu-target='id_bud_menu']"
        ).click()
        del_btn = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, "#id_bud_menu .btn-danger"
        ))
        self.assertEqual(del_btn.text.strip(), "DEL")
        del_btn.click()
        portal = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, "#id_guard_portal.active"
        ))
        self.assertIn(
            "Delete this bud?",
            portal.find_element(By.CSS_SELECTOR, ".guard-message").text,
        )

    def test_guard_del_removes_bud_from_m2m_and_navigates_away(self):
        self.browser.find_element(
            By.CSS_SELECTOR, ".gear-btn[data-menu-target='id_bud_menu']"
        ).click()
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, "#id_bud_menu .btn-danger"
        )).click()
        # Guard portal: DEL = .guard-yes, NVM = .guard-no.
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, "#id_guard_portal.active .guard-yes"
        )).click()
        self.wait_for(lambda: self.assertIn(
            "/billboard/my-buds/", self.browser.current_url
        ))
        self.assertNotIn(self.alice, list(self.gamer.buds.all()))

    def test_guard_nvm_dismisses_portal_without_navigating_or_deleting(self):
        self.browser.find_element(
            By.CSS_SELECTOR, ".gear-btn[data-menu-target='id_bud_menu']"
        ).click()
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, "#id_bud_menu .btn-danger"
        )).click()
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, "#id_guard_portal.active .guard-no"
        )).click()
        # Portal closes; bud is still in M2M; we're still on the bud page.
        self.wait_for(lambda: self.assertNotIn(
            "active",
            self.browser.find_element(By.ID, "id_guard_portal").get_attribute("class") or "",
        ))
        self.assertIn(self.alice, list(self.gamer.buds.all()))
        self.assertIn(f"/billboard/buds/{self.alice.id}/", self.browser.current_url)


class BudPageInviteCascadeTest(FunctionalTest):
    def setUp(self):
        super().setUp()
        self.gamer = User.objects.create(email="me@test.io", username="me")
        self.alice = User.objects.create(email="alice@test.io", username="alice")
        self.gamer.buds.add(self.alice)
        self.create_pre_authenticated_session("me@test.io")

    def test_no_pending_invite_no_glow_no_sea_active(self):
        self.browser.get(
            self.live_server_url + f"/billboard/buds/{self.alice.id}/"
        )
        burger = self.wait_for(lambda: self.browser.find_element(By.ID, "id_burger_btn"))
        self.assertNotIn("glow-handoff", (burger.get_attribute("class") or "").split())
        burger.click()
        sea = self.browser.find_element(By.ID, "id_sea_btn")
        self.assertNotIn("active", (sea.get_attribute("class") or "").split())

    def test_pending_invite_glows_burger_and_seats_sea_btn_active(self):
        """alice → me PENDING SeaInvite. burger.glow-handoff is rendered
        server-side (machine cascade — same `glow-handoff` class as the
        my_sea.html sea_first_draw_pending path)."""
        SeaInvite.objects.create(
            owner=self.alice,
            invitee_email=self.gamer.email,
            invitee=self.gamer,
            status=SeaInvite.PENDING,
        )
        self.browser.get(
            self.live_server_url + f"/billboard/buds/{self.alice.id}/"
        )
        burger = self.wait_for(lambda: self.browser.find_element(By.ID, "id_burger_btn"))
        self.assertIn("glow-handoff", (burger.get_attribute("class") or "").split())
        burger.click()
        sea = self.wait_for(lambda: self.browser.find_element(By.ID, "id_sea_btn"))
        self.assertIn("active", (sea.get_attribute("class") or "").split())

    def test_click_glowing_sea_btn_navigates_to_buds_my_sea_and_clears_glow(self):
        """Click glowing #id_sea_btn → navigates to alice's my-sea spectator
        page. The .active stays on sea_btn (preserved across pages); the
        burger's .glow-handoff is consumed by the handoff."""
        SeaInvite.objects.create(
            owner=self.alice,
            invitee_email=self.gamer.email,
            invitee=self.gamer,
            status=SeaInvite.PENDING,
        )
        self.browser.get(
            self.live_server_url + f"/billboard/buds/{self.alice.id}/"
        )
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_burger_btn")).click()
        sea = self.wait_for(lambda: self.browser.find_element(By.ID, "id_sea_btn"))
        sea.click()
        # Final URL is the bud's my-sea spectator page — exact route TBD
        # at IT time (likely /gameboard/my-sea/spectate/<owner_id>/ or
        # /gameboard/my-sea/<owner_id>/). The FT pins the OWNER's id in
        # the URL so the IT picks a route shape that surfaces it.
        self.wait_for(lambda: self.assertIn(
            str(self.alice.id), self.browser.current_url
        ))
        self.wait_for(lambda: self.assertIn("my-sea", self.browser.current_url))


class BudPageAutoAddOnFirstVisitTest(FunctionalTest):
    def setUp(self):
        super().setUp()
        self.gamer = User.objects.create(email="me@test.io", username="me")
        self.alice = User.objects.create(email="alice@test.io", username="alice")
        # NOTE: alice is NOT yet in gamer.buds — visiting bud.html for her
        # via the post-attribution link should auto-add her.
        self.create_pre_authenticated_session("me@test.io")

    def test_visiting_bud_page_for_non_bud_auto_adds_to_m2m(self):
        """Per-spec: 'if the inviter wasn't already a bud, navigating there
        as such automatically adds the bud to the user's bud list.' Same
        implicit-add posture as share-post → buds.add."""
        self.assertNotIn(self.alice, list(self.gamer.buds.all()))
        self.browser.get(
            self.live_server_url + f"/billboard/buds/{self.alice.id}/"
        )
        self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".bud-page-header"
        ))
        self.assertIn(self.alice, list(self.gamer.buds.all()))
