"""Tests for the browser ref system (annotate_snapshot / resolve_ref)."""

from __future__ import annotations

import pytest

from gcu.browser.refs import (
    RefEntry,
    annotate_snapshot,
    resolve_ref,
)

# ---------------------------------------------------------------------------
# annotate_snapshot
# ---------------------------------------------------------------------------

SAMPLE_SNAPSHOT = """\
- navigation "Main":
  - link "Home"
  - link "About"
- main:
  - heading "Welcome"
  - textbox "Search"
  - button "Submit"
  - paragraph: some text here
  - img "Logo"
  - list:
    - listitem:
      - link "Item 1"
    - listitem:
      - link "Item 2\""""


class TestAnnotateSnapshot:
    def test_assigns_refs_to_interactive_roles(self):
        annotated, ref_map = annotate_snapshot(SAMPLE_SNAPSHOT)
        # link, textbox, button should all get refs
        assert "[ref=e" in annotated
        # Check that specific interactive elements got refs
        roles_in_map = {entry.role for entry in ref_map.values()}
        assert "link" in roles_in_map
        assert "textbox" in roles_in_map
        assert "button" in roles_in_map

    def test_skips_structural_roles(self):
        annotated, ref_map = annotate_snapshot(SAMPLE_SNAPSHOT)
        roles_in_map = {entry.role for entry in ref_map.values()}
        # main (unnamed), list, listitem (unnamed), paragraph are structural — no refs.
        # Note: navigation is a landmark role and now gets a ref when named, so it
        # is not asserted absent here.
        assert "main" not in roles_in_map
        assert "list" not in roles_in_map
        assert "listitem" not in roles_in_map
        assert "paragraph" not in roles_in_map

    def test_named_content_roles_get_refs(self):
        annotated, ref_map = annotate_snapshot(SAMPLE_SNAPSHOT)
        roles_in_map = {entry.role for entry in ref_map.values()}
        # heading and img have names, so they should get refs
        assert "heading" in roles_in_map
        assert "img" in roles_in_map

    def test_unnamed_content_roles_skip(self):
        snapshot = "- heading\n- img"
        _, ref_map = annotate_snapshot(snapshot)
        # No names → no refs for content roles
        assert len(ref_map) == 0

    def test_preserves_non_matching_lines(self):
        snapshot = 'some random text\n- button "OK"\nanother line'
        annotated, _ = annotate_snapshot(snapshot)
        lines = annotated.split("\n")
        assert lines[0] == "some random text"
        assert lines[2] == "another line"

    def test_nth_disambiguation(self):
        snapshot = '- button "Save"\n- button "Save"\n- button "Cancel"'
        annotated, ref_map = annotate_snapshot(snapshot)

        # Two "Save" buttons should have nth=0 and nth=1
        save_entries = [(rid, e) for rid, e in ref_map.items() if e.role == "button" and e.name == "Save"]
        assert len(save_entries) == 2
        nths = sorted(e.nth for _, e in save_entries)
        assert nths == [0, 1]

        # "Cancel" should have nth=0
        cancel_entries = [e for e in ref_map.values() if e.role == "button" and e.name == "Cancel"]
        assert len(cancel_entries) == 1
        assert cancel_entries[0].nth == 0

    def test_sequential_ref_ids(self):
        snapshot = '- link "A"\n- link "B"\n- link "C"'
        _, ref_map = annotate_snapshot(snapshot)
        assert set(ref_map.keys()) == {"e0", "e1", "e2"}

    def test_empty_snapshot(self):
        annotated, ref_map = annotate_snapshot("")
        assert annotated == ""
        assert ref_map == {}


# ---------------------------------------------------------------------------
# resolve_ref
# ---------------------------------------------------------------------------


class TestResolveRef:
    def test_resolves_valid_ref(self):
        ref_map = {
            "e0": RefEntry(role="button", name="Submit", nth=0),
        }
        result = resolve_ref("e0", ref_map)
        assert result == '[role="button"][aria-label="Submit"]:nth-of-type(1)'

    def test_passes_through_css_selectors(self):
        ref_map = {"e0": RefEntry(role="button", name="OK", nth=0)}
        assert resolve_ref("#my-button", ref_map) == "#my-button"
        assert resolve_ref(".btn-primary", ref_map) == ".btn-primary"
        assert resolve_ref("div > button", ref_map) == "div > button"

    def test_passes_through_role_selectors(self):
        ref_map = {"e0": RefEntry(role="button", name="OK", nth=0)}
        sel = 'role=button[name="OK"]'
        assert resolve_ref(sel, ref_map) == sel

    def test_raises_on_unknown_ref(self):
        ref_map = {"e0": RefEntry(role="button", name="OK", nth=0)}
        with pytest.raises(ValueError, match="not found"):
            resolve_ref("e99", ref_map)

    def test_raises_when_no_ref_map(self):
        with pytest.raises(ValueError, match="no snapshot"):
            resolve_ref("e0", None)

    def test_quoted_name_passes_through(self):
        # Note: the CSS selector output does not currently escape inner quotes.
        # This produces technically-broken CSS when name contains double quotes,
        # but the bridge-based matcher appears to tolerate it. Tracked
        # separately as a follow-up.
        ref_map = {
            "e0": RefEntry(role="button", name='Say "Hello"', nth=0),
        }
        result = resolve_ref("e0", ref_map)
        assert result == '[role="button"][aria-label="Say "Hello""]:nth-of-type(1)'

    def test_no_name_produces_role_only_selector(self):
        ref_map = {
            "e0": RefEntry(role="textbox", name=None, nth=0),
        }
        result = resolve_ref("e0", ref_map)
        assert result == '[role="textbox"]:nth-of-type(1)'

    def test_empty_name(self):
        ref_map = {
            "e0": RefEntry(role="button", name="", nth=0),
        }
        result = resolve_ref("e0", ref_map)
        assert result == '[role="button"][aria-label=""]:nth-of-type(1)'

    def test_nth_in_selector(self):
        ref_map = {
            "e0": RefEntry(role="link", name="Next", nth=2),
        }
        result = resolve_ref("e0", ref_map)
        assert result == '[role="link"][aria-label="Next"]:nth-of-type(3)'


# ---------------------------------------------------------------------------
# Round-trip: annotate → resolve
# ---------------------------------------------------------------------------


class TestRoundTrip:
    def test_annotate_then_resolve(self):
        snapshot = '- button "Submit"\n- textbox "Email"\n- link "Home"'
        _, ref_map = annotate_snapshot(snapshot)

        # Each ref should resolve to a valid CSS selector (bridge-based API)
        for ref_id, entry in ref_map.items():
            resolved = resolve_ref(ref_id, ref_map)
            assert resolved.startswith(f'[role="{entry.role}"]')
            if entry.name is not None:
                assert f'[aria-label="{entry.name}"]' in resolved
            assert f":nth-of-type({entry.nth + 1})" in resolved

    def test_css_selectors_still_work_after_annotate(self):
        snapshot = '- button "OK"'
        _, ref_map = annotate_snapshot(snapshot)
        # CSS selectors pass through even when a ref_map exists
        assert resolve_ref("#submit-btn", ref_map) == "#submit-btn"
