"""
TDD Test Suite: Explore Grid Navigation Hardening
==================================================
Reproduces the exact production failure from 2026-04-16 22:59 where the bot:
1. Blacklisted ALL image_buttons because of generic semantic strings
2. Could not match "first image in explore grid" via the keyword fast-path
3. VLM picked row 3 instead of row 1 because the prompt lacks spatial ranking

These tests MUST fail before the fix and pass after.
"""

import re

from GramAddict.core.telepathic_engine import TelepathicEngine

# ── Realistic node fixtures extracted from live XML dump 2026-04-16_22-59-53 ──


def make_node(x, y, bounds, semantic, res_id="com.instagram.android:id/dummy", text="", desc=""):
    m = re.match(r"\[(\d+),(\d+)\]\[(\d+),(\d+)\]", bounds)
    l, t, r, b = map(int, m.groups()) if m else (0, 0, 0, 0)
    return {
        "x": x,
        "y": y,
        "width": r - l,
        "height": b - t,
        "area": (r - l) * (b - t),
        "raw_bounds": bounds,
        "semantic_string": semantic,
        "resource_id": res_id,
        "class_name": "android.widget.FrameLayout",
        "selected": False,
        "original_attribs": {"text": text, "desc": desc},
    }


EXPLORE_GRID_NODES = [
    # Row 1, Col 1 — this is what "first image" should match
    make_node(
        178,
        559,
        "[0,321][356,797]",
        "description: '4 photos by Patsy Weingart at row 1, column 1', id context: 'grid card layout container'",
        res_id="com.instagram.android:id/grid_card_layout_container",
        desc="4 photos by Patsy Weingart at row 1, column 1",
    ),
    # Row 1, Col 1 — child image_button (same area, no semantic info)
    make_node(
        178, 558, "[0,321][356,796]", "id context: 'image button'", res_id="com.instagram.android:id/image_button"
    ),
    # Row 1, Col 2
    make_node(
        540,
        559,
        "[362,321][718,797]",
        "description: 'Photo by Barbara at Row 1, Column 2', id context: 'grid card layout container'",
        res_id="com.instagram.android:id/grid_card_layout_container",
        desc="Photo by Barbara at Row 1, Column 2",
    ),
    make_node(
        540, 558, "[362,321][718,796]", "id context: 'image button'", res_id="com.instagram.android:id/image_button"
    ),
    # Row 2, Col 2
    make_node(
        540,
        1041,
        "[362,803][718,1279]",
        "description: 'Photo by Garima Bhaskar at Row 2, Column 2', id context: 'grid card layout container'",
        res_id="com.instagram.android:id/grid_card_layout_container",
        desc="Photo by Garima Bhaskar at Row 2, Column 2",
    ),
    make_node(
        540, 1040, "[362,803][718,1278]", "id context: 'image button'", res_id="com.instagram.android:id/image_button"
    ),
    # Row 3, Col 1 — this is what the VLM wrongly picked
    make_node(
        178,
        1523,
        "[0,1285][356,1761]",
        "description: '4 photos by Soul Of Nature Photography at row 3, column 1', id context: 'grid card layout container'",
        res_id="com.instagram.android:id/grid_card_layout_container",
        desc="4 photos by Soul Of Nature Photography at row 3, column 1",
    ),
    make_node(
        178, 1522, "[0,1285][356,1760]", "id context: 'image button'", res_id="com.instagram.android:id/image_button"
    ),
    # Search bar
    make_node(
        487,
        219,
        "[32,173][943,265]",
        "text: 'Search', id context: 'action bar search edit text'",
        res_id="com.instagram.android:id/action_bar_search_edit_text",
        text="Search",
    ),
]


class TestBlacklistPoisoning:
    """
    Bug: Generic semantic strings like 'id context: image button' get blacklisted,
    which kills ALL grid items because they share the same semantic string.
    """

    def test_generic_semantic_should_not_be_blacklistable(self):
        """
        A semantic string consisting ONLY of a generic id context (no text, no
        description) is too ambiguous to blacklist. The engine must refuse to
        blacklist it because it would poison all similar nodes.
        """
        engine = TelepathicEngine()
        # Clear any persisted state to test pure logic
        engine._blacklist = {}

        # Simulate the flow: the engine "clicked" an image_button and it failed
        TelepathicEngine._last_click_context = {
            "intent": "first image in explore grid",
            "semantic_string": "id context: 'image button'",
            "x": 178,
            "y": 558,
            "timestamp": 1000,
        }

        engine.reject_click("first image in explore grid")

        # The blacklist should NOT contain this generic entry
        blacklisted = engine._blacklist.get("first image in explore grid", [])
        assert "id context: 'image button'" not in blacklisted, (
            "CRITICAL: Generic semantic 'id context: image button' was blacklisted! "
            "This poisons ALL image_buttons in ALL grids."
        )


class TestExploreGridFastPath:
    """
    Bug: There was no fast-path to match 'first image in explore grid' to a
    grid_card_layout_container node. Now the Grid Fast-Path (Stage 1.25) handles
    this deterministically via resource-ID + spatial sorting.
    """

    def test_grid_fastpath_matches_container(self):
        """
        The Grid Fast-Path must match 'first image in explore grid' to
        a grid_card_layout_container node without calling VLM/embeddings.
        """
        engine = TelepathicEngine()

        # Build a minimal XML that the engine can parse — but we test the fast-path
        # directly by calling find_best_node with mocked extraction
        from unittest.mock import patch

        with patch.object(engine, "_extract_semantic_nodes", return_value=EXPLORE_GRID_NODES):
            with patch.object(engine, "_is_instagram_context", return_value=True):
                result = engine.find_best_node(
                    "<fake>", "first image in explore grid", min_confidence=0.82, device=None
                )

        assert result is not None, (
            "Grid Fast-Path returned None for 'first image in explore grid'. "
            "This forces every explore grid tap to use the expensive VLM fallback."
        )
        assert any(
            k in result.get("semantic_string", "").lower() for k in ["image button", "grid card layout container"]
        ), f"Grid Fast-Path selected wrong node type: {result.get('semantic_string')}"

    def test_grid_fastpath_prefers_topmost_row(self):
        """
        When multiple grid items match, the Grid Fast-Path must prefer the
        topmost one (smallest Y = row 1) since the intent says 'first'.
        """
        engine = TelepathicEngine()

        from unittest.mock import patch

        with patch.object(engine, "_extract_semantic_nodes", return_value=EXPLORE_GRID_NODES):
            with patch.object(engine, "_is_instagram_context", return_value=True):
                result = engine.find_best_node(
                    "<fake>", "first image in explore grid", min_confidence=0.82, device=None
                )

        if result is not None:
            # Row 1 items have y ≈ 559, Row 3 items have y ≈ 1523
            assert result["y"] < 800, (
                f"Grid Fast-Path selected a grid item at y={result['y']} (row 3+) "
                f"instead of row 1 (y≈559). The intent says 'first image'!"
            )


class TestVerifySuccessExploreGrid:
    """
    Bug: verify_success for explore grid tap checks for feed markers, but
    the post_load_timeout dump proved the bot was STILL on the explore grid.
    The verification correctly returned False, but the response was to blacklist
    the grid_card_layout_container — which is the WRONG reaction. The tap
    just didn't register; it doesn't mean the mapping is wrong.
    """

    def test_verify_success_returns_none_when_still_on_grid(self):
        """
        If we tapped a grid item but the screen still shows the explore grid
        (no feed markers), verify_success must return None (Inconclusive).
        """
        engine = TelepathicEngine()

        # Simulate that we just clicked a grid item
        TelepathicEngine._last_click_context = {
            "intent": "first image in explore grid",
            "semantic_string": "description: '4 photos by Patsy Weingart at row 1, column 1'",
            "x": 178,
            "y": 559,
            "timestamp": 1000,
        }

        # Post-click XML still shows the explore grid (no feed markers)
        still_on_grid_xml = """
        <node class="android.widget.FrameLayout">
            <node resource-id="com.instagram.android:id/explore_action_bar" />
            <node resource-id="com.instagram.android:id/grid_card_layout_container"
                  content-desc="4 photos by Patsy Weingart at row 1, column 1" />
            <node resource-id="com.instagram.android:id/image_button" />
        </node>
        """

        result = engine.verify_success("first image in explore grid", still_on_grid_xml)
        assert result is None, "verify_success should be inconclusive (None) when still on explore grid"

    def test_verify_success_returns_true_when_post_opened(self):
        """
        If the grid tap succeeded and we're now viewing a post with feed markers,
        verify_success must return True.
        """
        engine = TelepathicEngine()

        TelepathicEngine._last_click_context = {
            "intent": "first image in explore grid",
            "semantic_string": "description: '4 photos by Patsy Weingart at row 1, column 1'",
            "x": 178,
            "y": 559,
            "timestamp": 1000,
        }

        # Post-click XML shows a feed post (has feed markers)
        post_view_xml = """
        <node class="android.widget.FrameLayout">
            <node resource-id="com.instagram.android:id/row_feed_photo_imageview" />
            <node resource-id="com.instagram.android:id/row_feed_button_like" />
            <node resource-id="com.instagram.android:id/row_feed_button_comment" />
            <node resource-id="com.instagram.android:id/row_feed_button_share" />
        </node>
        """

        result = engine.verify_success("first image in explore grid", post_view_xml)
        assert result is True, "verify_success should pass when post view is visible"
