"""
Unit Test: Intent Resolver Back-Button Guard
==============================================
RED phase: Tests that the intent resolver's candidate filtering
MUST exclude Back/navigation buttons when searching for tab intents.

PRODUCTION BUG 2026-04-30:
VLM returned Back button for "tap profile tab" → account switch failed.
"""

from GramAddict.core.perception.spatial_parser import SpatialParser, SpatialNode


def _flatten_tree(node: SpatialNode):
    """Recursively flatten a SpatialNode tree into a list."""
    result = [node]
    for child in node.children:
        result.extend(_flatten_tree(child))
    return result


def _parse_all_nodes(xml_str):
    """Parse XML into flat list of SpatialNodes."""
    parser = SpatialParser()
    root = parser.parse(xml_str)
    if root is None:
        return []
    return _flatten_tree(root)


OTHER_PROFILE_XML_MINIMAL = """<?xml version="1.0" encoding="utf-8"?>
<hierarchy rotation="0">
    <node class="android.widget.FrameLayout" package="com.instagram.android"
          bounds="[0,0][1080,2400]">
        <node resource-id="com.instagram.android:id/action_bar_layout"
              class="android.widget.LinearLayout" bounds="[0,173][1080,320]">
            <node resource-id="com.instagram.android:id/action_bar_button_back"
                  class="android.widget.ImageView"
                  content-desc="Back" clickable="true"
                  bounds="[0,173][127,320]" />
            <node resource-id="com.instagram.android:id/action_bar_title"
                  class="android.widget.TextView" text="joannehollings"
                  content-desc="joannehollings"
                  bounds="[169,173][569,320]" />
        </node>
        <node resource-id="com.instagram.android:id/profile_header_full_name_above_vanity"
              class="android.widget.TextView" text="Joanne Hollings"
              bounds="[32,580][400,630]" />
        <node resource-id="com.instagram.android:id/tab_bar"
              class="android.widget.LinearLayout" bounds="[0,2300][1080,2400]">
            <node resource-id="com.instagram.android:id/feed_tab"
                  content-desc="Home" clickable="true" selected="false"
                  bounds="[0,2300][216,2400]" />
            <node resource-id="com.instagram.android:id/search_tab"
                  content-desc="Search and explore" clickable="true" selected="false"
                  bounds="[216,2300][432,2400]" />
            <node resource-id="com.instagram.android:id/profile_tab"
                  content-desc="Profile" clickable="true" selected="true"
                  bounds="[864,2300][1080,2400]" />
        </node>
    </node>
</hierarchy>
"""


def test_back_button_excluded_for_tab_intents():
    """
    RED: When resolving "tap profile tab", the _visual_discovery pre-filter
    must exclude any node with resource-id containing "back" or
    content-desc == "Back".

    This is the structural guard that would have prevented the bug.
    """
    nodes = _parse_all_nodes(OTHER_PROFILE_XML_MINIMAL)

    intent = "tap profile tab"
    intent_lower = intent.lower()

    # Replicate intent_resolver.py:252-260 pre-filter
    candidates = [
        n for n in nodes
        if 200 < n.area < 400000
        and "com.android.systemui" not in (n.resource_id or "")
    ]

    # Verify the Back button IS in candidates (precondition)
    back_present = any(
        "back" in (n.resource_id or "").lower()
        or (n.content_desc or "").lower() == "back"
        for n in candidates
    )
    assert back_present, (
        "Precondition failed: Back button not in candidates. "
        "Cannot test the filtering guard."
    )

    # NOW apply the guard that MUST exist in production code.
    # Import and test the actual production filter.
    from GramAddict.core.perception.intent_resolver import IntentResolver

    resolver = IntentResolver()
    filtered = resolver.filter_navigation_conflicts(candidates, intent)

    # Assert Back button is EXCLUDED after guard
    back_in_result = [
        n for n in filtered
        if "back" in (n.resource_id or "").lower()
        or (n.content_desc or "").lower() == "back"
    ]
    assert len(back_in_result) == 0, (
        f"Back button survived filtering for tab intent! "
        f"Nodes: {[(n.resource_id, n.content_desc) for n in back_in_result]}"
    )

    # Assert profile tab IS still in the filtered results
    profile_tabs = [
        n for n in filtered
        if "profile_tab" in (n.resource_id or "")
        or "profile" in (n.content_desc or "").lower()
    ]
    assert len(profile_tabs) > 0, (
        "Profile tab was also filtered out! Over-filtering."
    )


def test_back_button_kept_for_non_tab_intents():
    """
    The Back button guard must ONLY apply for tab intents.
    For "press back" or "tap back button", Back must be kept.
    """
    nodes = _parse_all_nodes(OTHER_PROFILE_XML_MINIMAL)
    candidates = [
        n for n in nodes
        if 200 < n.area < 400000
        and "com.android.systemui" not in (n.resource_id or "")
    ]

    from GramAddict.core.perception.intent_resolver import IntentResolver

    resolver = IntentResolver()

    # For "tap back button", the Back button MUST survive
    filtered = resolver.filter_navigation_conflicts(candidates, "tap back button")
    back_nodes = [
        n for n in filtered
        if "back" in (n.resource_id or "").lower()
        or (n.content_desc or "").lower() == "back"
    ]
    assert len(back_nodes) > 0, (
        "Back button was filtered out for 'tap back button' intent! "
        "The guard is too aggressive."
    )
