"""
Follow Verification Integrity Tests — RED Phase (TDD)

These tests prove the LIES in our current test suite.
Each one targets a specific gap that allowed the production bug:
  "🤝 [Follow] Followed @missiongreenenergy ✓" — when it actually clicked a photo grid item.

Root cause chain:
  1. VLM hallucinated a follow button (picked a photo)
  2. verify_success() asked the VLM again, VLM said "yes"
  3. No structural cross-check caught the mismatch
  4. FollowPlugin logged success based on nav_graph.do() return
  5. Qdrant memory was poisoned with a false positive

Each test MUST fail (RED) before any production code is fixed.
"""

from GramAddict.core.perception.action_memory import ActionMemory
from GramAddict.core.perception.spatial_parser import SpatialNode

# ═══════════════════════════════════════════════════════
# TEST 1: verify_success MUST reject wrong-element clicks for follow
# ═══════════════════════════════════════════════════════


class TestVerifySuccessRejectsWrongFollowElement:
    """
    Production scenario: The bot clicked '3 photos by Mission Green Energy'
    instead of a Follow button. verify_success() should have caught this.

    The VLM said "YES" because the screen changed (opening a photo).
    But the clicked element has NOTHING to do with 'follow'.
    """

    def setup_method(self):
        self.memory = ActionMemory()

    def test_follow_toggle_rejects_when_clicked_element_is_photo(self):
        """
        If the tracked click was on a photo grid item (desc='3 photos by ...'),
        verify_success for 'follow' MUST return False — regardless of VLM opinion.

        This is the ROOT CAUSE test. Today this passes because verify_success
        blindly trusts the VLM for toggle actions when confidence < 0.95.
        """
        # Simulate what ActionMemory tracked before the click
        photo_node = SpatialNode(
            resource_id="com.instagram.android:id/image_button",
            class_name="android.widget.ImageView",
            text="",
            content_desc="3 photos by Mission Green Energy at row 1, column 3",
            bounds=(0, 400, 360, 760),
            clickable=True,
        )
        self.memory.track_click("tap 'Follow' button", photo_node)

        # The XML changed (photo opened), but the intent was 'follow'
        pre_xml = "<hierarchy><node resource-id='profile_tab'/></hierarchy>"
        post_xml = "<hierarchy><node resource-id='media_viewer'/></hierarchy>"

        # The clicked element has NO relation to "follow" — desc is about photos
        # verify_success MUST detect this semantic mismatch structurally,
        # WITHOUT relying on VLM (which already lied once)
        result = self.memory.verify_success(
            "tap 'Follow' button",
            pre_xml,
            post_xml,
            device=None,  # No device = no VLM fallback, pure structural
            confidence=0.0,
        )

        # With device=None, it falls through to structural delta check.
        # Currently: diff > 0 for toggle → returns True (WRONG!)
        # The structural delta only checks length diff, not semantic match.
        #
        # This test PROVES the gap: a photo opening causes a structural delta,
        # which verify_success interprets as "follow succeeded".
        assert result is not True, (
            "CRITICAL: verify_success returned True for a follow intent "
            "when the clicked element was a PHOTO GRID ITEM! "
            "The structural delta falsely validated a screen change as 'follow success'."
        )

    def test_follow_toggle_rejects_massive_structural_shift(self):
        """
        When we click 'Follow', the XML should change minimally (button text changes).
        If the XML changes massively (>1000 chars), it means we navigated away.
        The current code DOES have this check, but only for diff > 1000.
        A photo view change can be 500-900 chars — slipping under the radar.
        """
        pre_xml = "x" * 10000  # Simulated profile page
        post_xml = "y" * 10500  # Simulated photo view (500 chars diff)

        photo_node = SpatialNode(
            resource_id="com.instagram.android:id/image_button",
            class_name="android.widget.ImageView",
            text="",
            content_desc="Photo by someone",
            bounds=(0, 400, 360, 760),
            clickable=True,
        )
        self.memory.track_click("tap 'Follow' button", photo_node)

        result = self.memory.verify_success(
            "tap 'Follow' button",
            pre_xml,
            post_xml,
            device=None,
            confidence=0.0,
        )

        # 500 chars diff is > 0 but < 1000, so current code returns True
        # But the CLICKED element was a photo, not a follow button!
        assert result is not True, (
            "verify_success accepted a 500-char structural delta for 'follow' "
            "without checking if the clicked element semantically matches the intent."
        )


# ═══════════════════════════════════════════════════════
# TEST 2: QNavGraph.do() MUST block follow when no Follow button exists
# ═══════════════════════════════════════════════════════


class TestQNavGraphDoBlocksFollowWithoutButton:
    """
    QNavGraph.do() has screen-sanity checks for 'like', 'comment', 'share'
    but NOT for 'follow'. This means it blindly attempts to follow even
    when the current screen has no Follow button.
    """

    def test_do_rejects_follow_when_not_in_available_actions(self, make_real_device_with_image, e2e_configs):
        """
        If the current screen's available_actions does not contain 'tap follow button',
        QNavGraph.do("tap 'Follow' button") MUST return False immediately.

        Currently: 'follow' is NOT in the action_checks map at q_nav_graph.py:L137-141,
        so it NEVER gets checked. The bot blindly passes through to GOAP.
        """
        from GramAddict.core.q_nav_graph import QNavGraph
        from GramAddict.core.session_state import SessionState

        device = make_real_device_with_image(None, "<hierarchy/>")

        SessionState(e2e_configs)

        nav = QNavGraph(device)

        result = nav.do("tap 'Follow' button")

        assert result is False, (
            "QNavGraph.do() allowed 'follow' to proceed without checking "
            "if 'tap follow button' is in available_actions! "
            "The action_checks map at q_nav_graph.py:L137 is missing 'follow'."
        )


# ═══════════════════════════════════════════════════════
# TEST 3: ActionMemory.confirm_click() MUST NOT poison Qdrant with mismatched intents
# ═══════════════════════════════════════════════════════


class TestActionMemoryNeverConfirmsMismatch:
    """
    After a false VLM verification, confirm_click() stores the wrong
    click mapping in Qdrant. Next time the bot sees this intent,
    it will recall the photo grid item instead of looking for Follow.
    """

    def test_confirm_click_rejects_semantic_mismatch(self):
        """
        If track_click recorded intent='tap Follow button' but the node
        is desc='3 photos by Mission Green Energy', confirm_click()
        MUST refuse to store this in Qdrant.

        Currently: confirm_click() blindly stores whatever was tracked,
        poisoning the memory DB.
        """
        # Wipe DB to prevent state leak from previous tests poisoning the retrieve_memory assertion
        from GramAddict.core.qdrant_memory import UIMemoryDB

        db = UIMemoryDB()
        db.wipe_collection()

        memory = ActionMemory(ui_memory=db)

        # Track a click on the WRONG element
        wrong_node = SpatialNode(
            resource_id="com.instagram.android:id/image_button",
            class_name="android.widget.ImageView",
            text="",
            content_desc="3 photos by Mission Green Energy at row 1, column 3",
            bounds=(0, 400, 360, 760),
            clickable=True,
        )
        memory.track_click("tap 'Follow' button", wrong_node)

        # Production flow calls confirm_click after VLM says "yes"
        memory.confirm_click("tap 'Follow' button")

        # Qdrant store_memory should NOT have been called because
        # the element has nothing to do with 'follow'
        # Since we use the real ActionMemory and Qdrant backend, we can verify
        # that the memory wasn't stored by checking retrieve_memory directly.
        from GramAddict.core.qdrant_memory import UIMemoryDB

        db = UIMemoryDB()
        assert db.retrieve_memory("tap 'Follow' button", "") is None, (
            "CRITICAL: ActionMemory.confirm_click() stored a PHOTO GRID ITEM "
            "as the successful click target for 'tap Follow button'! "
            "This poisons Qdrant and causes the same wrong click on every future run."
        )


# ═══════════════════════════════════════════════════════
# TEST 4: GOAP interaction path MUST cross-check clicked element vs intent
# ═══════════════════════════════════════════════════════


class TestGOAPInteractionCrossCheck:
    """
    GOAP._execute_action() trusts VLM twice:
    1. VLM selects the element to click
    2. VLM verifies if the click was successful

    If VLM #1 hallucinated, VLM #2 will also lie (confirmation bias).
    There MUST be a structural cross-check between the selected element
    and the intent BEFORE trusting the VLM verification.
    """

    def test_execute_action_rejects_when_clicked_node_doesnt_match_intent(
        self, make_real_device_with_image, e2e_configs
    ):
        """
        If find_best_node returns a node with desc='3 photos by ...'
        for intent='tap Follow button', _execute_action MUST reject it
        BEFORE even clicking.

        Currently: _execute_action clicks first, then asks VLM to verify.
        The VLM verification is the fox guarding the henhouse.
        """
        from GramAddict.core.goap import GoalExecutor

        xml_dump = """<?xml version="1.0" encoding="UTF-8"?>
<hierarchy>
  <node resource-id="com.instagram.android:id/image_button"
        class="android.widget.ImageView"
        content-desc="3 photos by Mission Green Energy at row 1, column 3"
        bounds="[0,400][360,760]" />
</hierarchy>"""

        device = make_real_device_with_image(None, xml_dump)

        # Track shell calls to verify no native click/swipe happened
        device.shell_calls = []

        def tracking_shell(cmd):
            device.shell_calls.append(cmd)

        device.deviceV2.shell = tracking_shell

        executor = GoalExecutor(device, bot_username="testbot")

        # No perceive mocking: the real ScreenIdentity will classify <hierarchy/> as OBSTACLE_FOREIGN_APP
        # which means available_actions is empty.

        result = executor._execute_action("tap 'Follow' button")

        # The method should have rejected this node BEFORE clicking
        assert result is False, (
            "GOAP._execute_action accepted a PHOTO GRID ITEM for 'tap Follow button'! "
            "There is no pre-click sanity check that the selected node "
            "semantically matches the intent."
        )
        # Verify that device.deviceV2.shell was NOT called
        assert len(device.shell_calls) == 0


# ═══════════════════════════════════════════════════════
# TEST 5: FollowPlugin.execute() E2E — end-to-end truth test
# ═══════════════════════════════════════════════════════


class TestFollowPluginEndToEnd:
    """
    The most critical gap: FollowPlugin.execute() is never tested E2E.
    It calls nav_graph.do("tap 'Follow' button") and trusts the boolean.
    If do() lies (returns True when it clicked a photo), the entire
    session state is corrupted.
    """

    def test_follow_plugin_does_not_count_follow_when_wrong_element_clicked(
        self, make_real_device_with_image, e2e_configs
    ):
        """
        By removing lying mocks, we test the REAL E2E behavior:
        If we give the plugin a screen with NO follow button, QNavGraph.do()
        will correctly return False (thanks to our structural guards), and
        the FollowPlugin will NOT record a false follow in session_state.
        """
        from GramAddict.core.behaviors import BehaviorContext
        from GramAddict.core.behaviors.follow import FollowPlugin

        plugin = FollowPlugin()

        e2e_configs.args.follow_percentage = 100
        e2e_configs.args.current_likes_limit = 300

        if "follow" not in e2e_configs.config["plugins"]:
            e2e_configs.config["plugins"]["follow"] = {}
        e2e_configs.config["plugins"]["follow"]["percentage"] = 100

        from GramAddict.core.session_state import SessionState

        session_state = SessionState(e2e_configs)
        session_state.added_interactions = []

        original_add_interaction = session_state.add_interaction

        def spy_add_interaction(source, succeed, followed, scraped):
            session_state.added_interactions.append(
                {"source": source, "succeed": succeed, "followed": followed, "scraped": scraped}
            )
            original_add_interaction(source, succeed, followed, scraped)

        session_state.add_interaction = spy_add_interaction

        from GramAddict.core.q_nav_graph import QNavGraph

        xml_dump = """<?xml version="1.0" encoding="UTF-8"?>
<hierarchy>
  <node resource-id="com.instagram.android:id/image_button"
        class="android.widget.ImageView"
        content-desc="3 photos by Mission Green Energy at row 1, column 3"
        bounds="[0,400][360,760]" />
</hierarchy>"""

        device = make_real_device_with_image(None, xml_dump)
        nav_graph = QNavGraph(device)

        ctx = BehaviorContext(
            device=device,
            session_state=session_state,
            configs=e2e_configs,
            username="missiongreenenergy",
            cognitive_stack={"nav_graph": nav_graph},
        )

        result = plugin.execute(ctx)

        assert result.executed is False, "Expected plugin to report executed=False since there is no follow button"
        assert len(session_state.added_interactions) == 0, "No follow interaction should have been recorded!"
