"""FT for the My Buds page — bud btn + slide-out add flow.

Phase 1 of the buds sprint: explicit add via my_buds.html. Phase 2
will layer autocomplete (sky-place-style top-3 username suggestions) and
implicit auto-add on post-share / gate-invite.
"""
from selenium.webdriver.common.by import By

from apps.lyric.models import User

from .base import FunctionalTest


class MyBudsPageTest(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.create_pre_authenticated_session("me@test.io")

    def test_renders_existing_buds(self):
        """Pre-existing buds show up as entries on first render."""
        self.gamer.buds.add(self.alice)
        self.browser.get(self.live_server_url + "/billboard/my-buds/")
        entry = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".bud-entry .bud-name")
        )
        # Bud names render via `at_handle` filter — `@<username>` w. an
        # `@` prefix on users w. a username; truncated email otherwise.
        self.assertEqual(entry.text, "@alice")

    def test_empty_state_when_no_buds(self):
        self.browser.get(self.live_server_url + "/billboard/my-buds/")
        empty = self.wait_for(
            lambda: self.browser.find_element(By.CSS_SELECTOR, ".applet-list-entry--empty")
        )
        self.assertIn("No buds yet", empty.text)

    def test_add_bud_via_bud_btn_appends_entry(self):
        self.browser.get(self.live_server_url + "/billboard/my-buds/")
        btn = self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_btn"))
        btn.click()

        recipient = self.wait_for(lambda: self.browser.find_element(By.ID, "id_recipient"))
        recipient.send_keys("alice@test.io")
        ok = self.browser.find_element(By.CSS_SELECTOR, "#id_bud_panel .btn.btn-confirm")
        ok.click()

        # New entry appears w. alice's @-prefixed handle (not the bare email)
        self.wait_for(lambda: self.assertEqual(
            self.browser.find_element(
                By.CSS_SELECTOR, f".bud-entry[data-bud-id='{self.alice.id}'] .bud-name"
            ).text,
            "@alice",
        ))
        # Server-side persisted
        self.wait_for(lambda: self.assertIn(
            self.alice, list(self.gamer.buds.all())
        ))
        # "No buds yet" empty-state row dismisses async as the first bud lands
        # (shell partial renders w. .applet-list-entry--empty, not .bud-entry--empty)
        self.wait_for(lambda: self.assertEqual(
            self.browser.find_elements(By.CSS_SELECTOR, ".applet-list-entry--empty"),
            [],
        ))

    def test_async_added_entry_matches_server_row_and_populates_tooltip(self):
        """Regression 2026-05-29: the async-appended row used to be a stripped
        `@handle`-only `<li>` — no anchor, no ` the <Title>`, no data-tt-*
        attrs — so its tooltip rendered empty (vs. the server-rendered rows
        above it). The appended row must mirror _my_buds_item.html exactly."""
        self.browser.get(self.live_server_url + "/billboard/my-buds/")
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_btn")).click()
        recipient = self.wait_for(lambda: self.browser.find_element(By.ID, "id_recipient"))
        recipient.send_keys("alice@test.io")
        self.browser.find_element(
            By.CSS_SELECTOR, "#id_bud_panel .btn.btn-confirm"
        ).click()

        sel = f".bud-entry[data-bud-id='{self.alice.id}']"
        row = self.wait_for(lambda: self.browser.find_element(By.CSS_SELECTOR, sel))
        # The trailing `.applet-list-buffer` spacer must stay LAST — the new
        # row goes BEFORE it, not after (regression: querying the wrong buffer
        # class dropped the row below the spacer).
        last = self.browser.find_element(
            By.CSS_SELECTOR, "#id_buds_list > li:last-child"
        )
        self.assertIn("applet-list-buffer", (last.get_attribute("class") or "").split())
        # Carries both classes (styling + tooltip-lock both key on these).
        cls = (row.get_attribute("class") or "").split()
        self.assertIn("applet-list-entry", cls)
        self.assertIn("bud-entry", cls)
        # data-tt-* attrs the portal reads on row-lock.
        self.assertEqual(row.get_attribute("data-tt-title"), "@alice")
        self.assertEqual(row.get_attribute("data-tt-description"), "Earthman")
        self.assertEqual(row.get_attribute("data-tt-email"), "alice@test.io")
        # Anchor routes into the bud landing page; trailing ` the <Title>`.
        anchor = row.find_element(By.CSS_SELECTOR, ".bud-name a")
        self.assertIn(f"/billboard/buds/{self.alice.id}/", anchor.get_attribute("href"))
        self.assertIn("the Earthman", row.find_element(
            By.CSS_SELECTOR, ".bud-row-title"
        ).text)
        # Click the row (NOT the anchor) → portal locks + populates non-empty.
        row.find_element(By.CSS_SELECTOR, ".bud-row-title").click()
        portal = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, "#id_tooltip_portal.active"
        ))
        self.assertEqual(
            portal.find_element(By.CSS_SELECTOR, ".tt-title").text, "@alice"
        )
        self.assertEqual(
            portal.find_element(By.CSS_SELECTOR, ".tt-description").text, "Earthman"
        )

    def test_no_autocomplete_suggestions_on_my_buds_page(self):
        """The bud-autocomplete pool is request.user.buds — surfacing buds
        you've already added on the page where you ADD new buds is just
        noise. Post-share + gatekeeper-invite panels keep it (re-sharing
        with an existing bud is a real flow); the My Buds add-panel drops
        the autocomplete bindings entirely. Absence of #id_bud_suggestions
        is the deterministic check (no debounce-window races)."""
        self.gamer.buds.add(self.alice)
        self.browser.get(self.live_server_url + "/billboard/my-buds/")
        self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_btn"))

        self.assertEqual(
            self.browser.find_elements(By.ID, "id_bud_suggestions"),
            [],
        )

    def test_add_unregistered_email_is_silent_noop(self):
        self.browser.get(self.live_server_url + "/billboard/my-buds/")
        btn = self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_btn"))
        btn.click()
        recipient = self.wait_for(lambda: self.browser.find_element(By.ID, "id_recipient"))
        recipient.send_keys("ghost@test.io")
        ok = self.browser.find_element(By.CSS_SELECTOR, "#id_bud_panel .btn.btn-confirm")
        ok.click()

        # Wait for the panel close (a positive signal the request landed)
        self.wait_for(lambda: self.assertNotIn(
            "active", btn.get_attribute("class")
        ))
        # No bud entries (the empty-state row has its own --empty class)
        entries = self.browser.find_elements(By.CSS_SELECTOR, ".bud-entry")
        self.assertEqual(len(entries), 0)

    def test_re_add_existing_bud_shows_already_present_brief_and_fyi_flashes_bud_name(self):
        """Re-adding an existing bud: server returns already_present=true;
        client renders the error Brief titled `@alice is already present`;
        clicking FYI dismisses the Brief AND adds .bud-duplicate-flash to
        the existing .bud-name cell."""
        self.gamer.buds.add(self.alice)
        self.browser.get(self.live_server_url + "/billboard/my-buds/")
        btn = self.wait_for(lambda: self.browser.find_element(By.ID, "id_bud_btn"))
        btn.click()
        recipient = self.wait_for(lambda: self.browser.find_element(By.ID, "id_recipient"))
        recipient.send_keys("alice@test.io")
        self.browser.find_element(By.CSS_SELECTOR, "#id_bud_panel .btn.btn-confirm").click()

        # Error Brief appears w. the duplicate title
        title = self.wait_for(lambda: self.browser.find_element(
            By.CSS_SELECTOR, ".note-banner--duplicate .note-banner__title"
        ))
        self.assertEqual(title.text, "@alice is already present")

        # Only one .bud-entry — no second alice row appended
        self.assertEqual(
            len(self.browser.find_elements(By.CSS_SELECTOR, ".bud-entry")),
            1,
        )

        # Pre-flash: the .bud-name carries no flash class yet
        bud_name = self.browser.find_element(
            By.CSS_SELECTOR, f".bud-entry[data-bud-id='{self.alice.id}'] .bud-name"
        )
        self.assertNotIn("bud-duplicate-flash", bud_name.get_attribute("class") or "")

        # Click FYI → Brief dismisses AND .bud-name gets the flash class
        self.browser.find_element(By.CSS_SELECTOR, ".note-banner--duplicate .note-banner__fyi").click()
        self.wait_for(lambda: self.assertEqual(
            self.browser.find_elements(By.CSS_SELECTOR, ".note-banner--duplicate"),
            [],
        ))
        self.wait_for(lambda: self.assertIn(
            "bud-duplicate-flash", bud_name.get_attribute("class") or ""
        ))
