"""
E2E: Situational Awareness Engine (SAE) Integrity
=================================================
Tests the deterministic perception and planning logic of the SAE.
Ensures that the bot correctly identifies obstacles via structural fast-paths
without relying on brittle mock LLM responses.
"""

import pytest

from GramAddict.core.situational_awareness import SituationalAwarenessEngine, SituationType


@pytest.fixture
def sae(make_real_device_with_xml):
    SituationalAwarenessEngine.reset()
    device = make_real_device_with_xml("")
    engine = SituationalAwarenessEngine.get_instance(device)
    # Clear the global ScreenMemoryDB so tests don't pollute each other
    from GramAddict.core.qdrant_memory import ScreenMemoryDB

    db = ScreenMemoryDB()
    db.wipe_collection()
    return engine


class TestSAEPerception:
    """Tests the perception fast-paths in SAE."""

    def test_perceive_empty_xml_is_foreign_app(self, sae):
        """Empty or invalid XML should be considered a foreign app / unknown state."""
        assert sae.perceive("") == SituationType.OBSTACLE_FOREIGN_APP
        assert sae.perceive(None) == SituationType.OBSTACLE_FOREIGN_APP

    def test_perceive_locked_screen(self, sae):
        """If the hardware reports the screen is off, it must be LOCKED_SCREEN."""
        sae.device.deviceV2.info = {"screenOn": False}
        xml_dump = '<node package="com.android.systemui" />'
        assert sae.perceive(xml_dump) == SituationType.OBSTACLE_LOCKED_SCREEN

    def test_perceive_action_blocked(self, sae):
        """Critical account safety check: 'Action Blocked' must return DANGER_ACTION_BLOCKED."""
        xml_dump = """
        <node package="com.instagram.android">
            <node resource-id="android:id/dialog" />
            <node text="Action Blocked" />
            <node text="Try again later to protect our community." />
        </node>
        """
        assert sae.perceive(xml_dump) == SituationType.DANGER_ACTION_BLOCKED

    def test_perceive_system_dialog(self, sae):
        """System permission dialogs must be identified structurally."""
        xml_dump = """
        <node package="com.google.android.permissioncontroller">
            <node text="Allow Instagram to record audio?" />
        </node>
        """
        assert sae.perceive(xml_dump) == SituationType.OBSTACLE_SYSTEM

    def test_perceive_creation_flow_modal(self, sae):
        """
        Creation flows (camera, story gallery) are inside Instagram but block navigation.
        They must be caught by structural markers.
        """
        xml_dump = """
        <node package="com.instagram.android">
            <node resource-id="com.instagram.android:id/quick_capture_button" />
        </node>
        """
        assert sae.perceive(xml_dump) == SituationType.OBSTACLE_MODAL

    def test_perceive_story_gallery_cancel_modal(self, sae):
        """Story gallery cancel button blocks navigation."""
        xml_dump = """
        <node package="com.instagram.android">
            <node resource-id="com.instagram.android:id/gallery_cancel_button" />
        </node>
        """
        assert sae.perceive(xml_dump) == SituationType.OBSTACLE_MODAL


class TestSAECompression:
    """Tests the XML compression logic to ensure signatures are compact but retain identity."""

    def test_compress_strips_noise_keeps_important_data(self, sae):
        xml_dump = """
        <?xml version="1.0" encoding="UTF-8"?>
        <hierarchy rotation="0">
            <node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2400]">
                <node text="Following" resource-id="com.instagram.android:id/follow_button" clickable="true" bounds="[10,10][100,50]" />
                <node content-desc="Profile Picture" clickable="true" />
            </node>
        </hierarchy>
        """
        compressed = sae._compress_xml(xml_dump)

        # Must extract packages
        assert "PACKAGES: com.instagram.android" in compressed
        # Must extract resource-ids without the package prefix
        assert "id=follow_button" in compressed
        # Must extract text
        assert "text='Following'" in compressed
        # Must extract content-desc
        assert "desc='Profile Picture'" in compressed
        # Must preserve CLICKABLE
        assert "CLICKABLE" in compressed

    def test_compress_handles_broken_xml(self, sae):
        """If XML is malformed, it should use regex fallback."""
        broken_xml = '<<node package="com.instagram.android" text="Hello" />'
        compressed = sae._compress_xml(broken_xml)
        assert "PACKAGES: com.instagram.android" in compressed
        assert "TEXTS: Hello" in compressed


class TestSAELoop:
    """Tests the higher-level engine loops."""

    def test_ensure_clear_screen_returns_true_immediately_if_normal(self, sae):
        """If the screen is NORMAL, the SAE should return True without taking actions."""
        xml_dump = """
        <node package="com.instagram.android">
            <node resource-id="com.instagram.android:id/row_feed_button_like" />
        </node>
        """
        # Inject the XML into the emulator's state instead of bypassing dump_hierarchy
        sae.device.deviceV2.info["screenOn"] = True
        sae.device.deviceV2.xml = xml_dump
        assert sae.ensure_clear_screen(max_attempts=3) is True
        assert sae._consecutive_failures == 0

    def test_is_instagram_foreground(self, sae):
        xml_dump = """
        <node package="com.instagram.android">
            <node resource-id="com.instagram.android:id/row_feed_button_like" />
        </node>
        """
        assert sae.is_instagram_foreground(xml_dump=xml_dump) is True

        foreign_xml = '<node package="com.apple.ios" />'
        assert sae.is_instagram_foreground(xml_dump=foreign_xml) is False

    def test_false_positive_modal_infinite_loop_trap(self, sae, monkeypatch):
        """
        Reproduces a production trap where ScreenIdentity overrides Qdrant for structural
        markers (Priority 0). If the LLM identifies the modal as a false positive, it unlearns
        it in Qdrant. However, without the fix, the next time GOAP evaluates the state,
        ScreenIdentity STILL says MODAL because of Priority 0, triggering an infinite loop.
        This test ensures ScreenIdentity respects NORMAL memory overrides.
        """
        from pathlib import Path

        from GramAddict.core.perception.screen_identity import ScreenIdentity
        from GramAddict.core.qdrant_memory import ScreenMemoryDB

        db = ScreenMemoryDB()
        if not db.is_connected:
            pytest.skip("Qdrant is not running — this test requires vector store for NORMAL override storage")

        # Load real home feed XML
        xml_dump = Path("tests/e2e/fixtures/home_feed_real.xml").read_text()

        # Inject the Priority 0 structural marker
        xml_dump = xml_dump.replace(
            '<node index="0" text="" resource-id="android:id/content"',
            '<node resource-id="com.instagram.android:id/gallery_cancel_button" bounds="[0,0][100,100]" />\n<node index="0" text="" resource-id="android:id/content"',
        )

        sae.device.deviceV2.xml = xml_dump
        sae.device.deviceV2.info["screenOn"] = True

        # Simulate LLM unlearning by storing this exact state as NORMAL
        compressed = sae._compress_xml(xml_dump)
        db.store_screen(compressed, "NORMAL")

        identity = ScreenIdentity("testuser")
        # Ensure we inject the device so get_screenshot_b64 doesn't crash if it falls back
        identity.device = sae.device

        # The bug: this would return MODAL because of Priority 0, ignoring the DB
        # The fix: it should return HOME_FEED because is_normal_override = True skips the MODAL check
        result = identity.identify(xml_dump)

        assert (
            result["screen_type"].value == "home_feed"
        ), f"Infinite loop trap: Expected home_feed, got {result['screen_type'].value}"
