"""
Visual Intent Resolution Tests

These tests prove the bot resolves UI intents by SEEING the screen,
not by parsing XML text descriptions or regex-matching content-desc.

Architecture: Set-of-Mark (SoM) Visual Prompting
1. Parse XML → extract clickable node bounding boxes
2. Take screenshot → draw numbered bounding boxes on the image
3. Send annotated screenshot to VLM: "Which numbered box should I tap?"
4. VLM SEES the UI and picks the right box
5. Map box number back to XML node for precise coordinates

No regex. No string matching. No content-desc parsing. Pure vision.
"""

import base64
from io import BytesIO

import pytest

from GramAddict.core.perception.intent_resolver import IntentResolver
from GramAddict.core.perception.spatial_parser import SpatialParser


def _load_profile_xml():
    with open("tests/fixtures/user_profile_dump.xml", "r", encoding="utf-8") as f:
        return f.read()


# ═══════════════════════════════════════════════════════
# TEST 1: Visual Discovery produces an annotated image
# ═══════════════════════════════════════════════════════


def test_visual_discovery_creates_annotated_screenshot(make_real_device_with_image):
    """
    The IntentResolver's visual discovery mode must:
    1. Take a screenshot from the device
    2. Draw numbered bounding boxes around clickable candidates
    3. Produce a base64-encoded annotated image

    This is the foundation — the VLM can ONLY pick correctly if
    it SEES the actual UI with clear numbered markers.
    """
    xml = _load_profile_xml()
    parser = SpatialParser()
    root = parser.parse(xml)
    candidates = parser.get_clickable_nodes(root)

    device = make_real_device_with_image("tests/fixtures/user_profile_dump.jpg", xml)
    resolver = IntentResolver()

    annotated_b64, box_map = resolver._annotate_screenshot_with_candidates(device, candidates)

    # Must produce a non-empty base64 image
    assert annotated_b64 is not None
    assert len(annotated_b64) > 100, "Annotated image is suspiciously small"

    # Must be valid base64 → decodeable to a real image
    img_bytes = base64.b64decode(annotated_b64)
    from PIL import Image

    img = Image.open(BytesIO(img_bytes))
    assert img.size == (1080, 2424)

    # box_map must contain at least the followers and following nodes
    assert len(box_map) > 0, "No boxes were drawn on the screenshot"

    # Verify both counter areas got boxes
    following_boxes = [
        idx
        for idx, node in box_map.items()
        if ("following" in (node.content_desc or "").lower() or "following" in (node.text or "").lower())
        and "followers" not in (node.content_desc or "").lower()
        and "followers" not in (node.text or "").lower()
    ]
    followers_boxes = [
        idx
        for idx, node in box_map.items()
        if "followers" in (node.content_desc or "").lower() or "followers" in (node.text or "").lower()
    ]
    assert len(following_boxes) >= 1, "No box drawn around 'following' counter"
    assert len(followers_boxes) >= 1, "No box drawn around 'followers' counter"
    assert following_boxes[0] != followers_boxes[0], "Following and followers got the same box number!"


# ═══════════════════════════════════════════════════════
# TEST 2: Visual Discovery resolves intent visually
# ═══════════════════════════════════════════════════════


@pytest.mark.live_llm
def test_visual_discovery_finds_following_by_seeing(make_real_device_with_image):
    """
    LIVE VLM TEST: The bot SEES a screenshot with numbered boxes
    and visually identifies which box is the "following" counter.

    This is the ultimate autonomous test — no string matching,
    no content-desc parsing, no regex. Pure vision.
    """
    xml = _load_profile_xml()
    parser = SpatialParser()
    root = parser.parse(xml)
    candidates = parser.get_clickable_nodes(root)

    device = make_real_device_with_image("tests/fixtures/user_profile_dump.jpg", xml)
    resolver = IntentResolver()

    # Visual Discovery: Let the VLM SEE the screen
    result = resolver.resolve(
        "tap 'following' list",
        candidates,
        device,
    )

    assert result is not None, "Visual discovery returned None — VLM couldn't find 'following' on screen"

    # Verify it picked the FOLLOWING node, not followers
    selected_id = (result.resource_id or "").lower()
    selected_desc = (result.content_desc or "").lower()

    assert (
        "following" in selected_id or "following" in selected_desc
    ), f"Visual discovery picked wrong node! Got: id='{result.resource_id}', desc='{result.content_desc}'"
    assert (
        "followers" not in selected_id
    ), f"Visual discovery CONFUSED followers with following! Selected: id='{result.resource_id}'"


# ═══════════════════════════════════════════════════════
# TEST 3: Visual Discovery is the PRIMARY resolution path
# ═══════════════════════════════════════════════════════


@pytest.mark.live_llm
def test_resolve_uses_text_vlm_fallback_when_no_device(make_real_device_with_image):
    """
    When called WITHOUT a device (device=None), resolve() must fall back
    to the text-based VLM resolution instead of visual discovery.
    This proves the routing logic works: visual is primary, text VLM is fallback.
    """
    from GramAddict.core.perception.spatial_parser import SpatialNode

    resolver = IntentResolver()

    # A single candidate with a clear profile_tab match
    candidates = [
        SpatialNode(
            resource_id="com.instagram.android:id/profile_tab",
            class_name="android.widget.FrameLayout",
            text="",
            content_desc="Profile",
            bounds=(800, 2200, 1000, 2400),
            clickable=True,
        )
    ]

    # Without device, resolve must still work via text VLM fallback
    result = resolver.resolve("tap profile tab", candidates, screen_height=2400)
    assert result is not None, "Text VLM fallback failed to find profile_tab without a device"
    assert result.resource_id == "com.instagram.android:id/profile_tab"


@pytest.mark.live_llm
def test_visual_discovery_finds_profile_tab_by_seeing(make_real_device_with_image):
    """
    LIVE VLM TEST: The bot SEES a screenshot with numbered boxes
    and visually identifies which box is the 'profile tab'.
    This proves the prompt correctly guides the VLM to pick bottom navigation tabs
    without hardcoding resource IDs.
    """
    from GramAddict.core.perception.spatial_parser import SpatialParser

    with open("tests/fixtures/home_feed_with_ad.xml", "r", encoding="utf-8") as f:
        xml = f.read()

    parser = SpatialParser()
    root = parser.parse(xml)
    candidates = parser.get_clickable_nodes(root)

    # Use a real image so the VLM can actually see the UI
    device = make_real_device_with_image("tests/fixtures/home_feed_with_ad.jpg", xml)
    resolver = IntentResolver()

    # Visual Discovery: Let the VLM SEE the screen
    result = resolver.resolve(
        "tap profile tab",
        candidates,
        device,
    )

    assert result is not None, "Visual discovery returned None — VLM couldn't find 'profile tab' on screen"

    # Check that it actually selected the correct tab
    selected_id = (result.resource_id or "").lower()

    # On the home_feed_with_ad_dump, the profile tab should be selected
    assert (
        "profile_tab" in selected_id
    ), f"Visual discovery picked wrong node! Got: id='{result.resource_id}', desc='{result.content_desc}'"
