"""
SAE E2E Tests: Situational Awareness Engine
Tests autonomous recovery from foreign apps, unknown modals, and learning.
Uses REAL XML dumps from production sessions.
"""

import os

from GramAddict.core.situational_awareness import (
    SituationalAwarenessEngine,
    SituationType,
)

# ─────────────────────────────────────────────────────
# Test Fixtures: Real-world XML scenarios
# ─────────────────────────────────────────────────────


GOOGLE_SEARCH_XML = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.google.android.googlequicksearchbox" content-desc="" clickable="false" bounds="[0,0][1080,2400]">
    <node index="0" text="" resource-id="com.google.android.googlequicksearchbox:id/search_box" class="android.widget.EditText" package="com.google.android.googlequicksearchbox" content-desc="Search" clickable="true" bounds="[50,200][1030,300]" />
    <node index="1" text="Close" resource-id="com.google.android.googlequicksearchbox:id/close_button" class="android.widget.ImageButton" package="com.google.android.googlequicksearchbox" content-desc="Close" clickable="true" bounds="[980,200][1050,280]" />
  </node>
</hierarchy>"""

INSTAGRAM_HOME_XML = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="" clickable="false" bounds="[0,0][1080,2400]">
    <node index="0" text="" resource-id="com.instagram.android:id/feed_tab" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="Home" clickable="true" selected="true" bounds="[0,2235][216,2361]" />
    <node index="1" text="" resource-id="com.instagram.android:id/search_tab" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="Search and Explore" clickable="true" bounds="[216,2235][432,2361]" />
    <node index="2" text="" resource-id="com.instagram.android:id/profile_tab" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="Profile" clickable="true" bounds="[864,2235][1080,2361]" />
  </node>
</hierarchy>"""

INSTAGRAM_SURVEY_XML = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="" clickable="false" bounds="[0,0][1080,2400]">
    <node index="0" text="" resource-id="com.instagram.android:id/feed_tab" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="Home" clickable="true" bounds="[0,2235][216,2361]" />
    <node index="1" text="" resource-id="com.instagram.android:id/survey_overlay_container" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="" clickable="false" bounds="[0,800][1080,2000]">
      <node text="How are you enjoying Instagram?" resource-id="com.instagram.android:id/survey_title" class="android.widget.TextView" package="com.instagram.android" clickable="false" bounds="[100,900][980,1000]" />
      <node text="Not Now" resource-id="com.instagram.android:id/button_negative" class="android.widget.Button" package="com.instagram.android" clickable="true" bounds="[100,1800][540,1900]" />
      <node text="Take Survey" resource-id="com.instagram.android:id/button_positive" class="android.widget.Button" package="com.instagram.android" clickable="true" bounds="[540,1800][980,1900]" />
    </node>
  </node>
</hierarchy>"""

UNKNOWN_MODAL_XML = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="" clickable="false" bounds="[0,0][1080,2400]">
    <node index="0" text="" resource-id="com.instagram.android:id/feed_tab" class="android.widget.FrameLayout" package="com.instagram.android" content-desc="Home" clickable="true" bounds="[0,2235][216,2361]" />
    <node text="" resource-id="com.instagram.android:id/mystery_interstitial_container" class="android.widget.FrameLayout" package="com.instagram.android" clickable="false" bounds="[0,500][1080,2200]">
      <node text="Please leave a rating!" resource-id="" class="android.widget.TextView" package="com.instagram.android" clickable="false" bounds="[100,600][980,700]" />
      <node text="not now" resource-id="" class="android.widget.Button" package="com.instagram.android" clickable="true" bounds="[100,2000][540,2100]" />
      <node text="rate 5 stars" resource-id="" class="android.widget.Button" package="com.instagram.android" clickable="true" bounds="[540,2000][980,2100]" />
    </node>
  </node>
</hierarchy>"""

PERMISSION_DIALOG_XML = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.permissioncontroller" content-desc="" clickable="false" bounds="[0,0][1080,2400]">
    <node text="" resource-id="com.android.permissioncontroller:id/grant_dialog" class="android.widget.LinearLayout" package="com.android.permissioncontroller" clickable="false" bounds="[100,800][980,1600]">
      <node text="Allow Instagram to access your location?" resource-id="" class="android.widget.TextView" package="com.android.permissioncontroller" clickable="false" bounds="[150,900][930,1000]" />
      <node text="Deny" resource-id="com.android.permissioncontroller:id/permission_deny_button" class="android.widget.Button" package="com.android.permissioncontroller" clickable="true" bounds="[150,1400][530,1500]" />
      <node text="Allow" resource-id="com.android.permissioncontroller:id/permission_allow_button" class="android.widget.Button" package="com.android.permissioncontroller" clickable="true" bounds="[550,1400][930,1500]" />
    </node>
  </node>
</hierarchy>"""

LOCK_SCREEN_XML = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.systemui" content-desc="" clickable="false" bounds="[0,0][1080,2400]">
    <node index="0" text="" resource-id="com.android.systemui:id/keyguard_status_view" class="android.widget.FrameLayout" package="com.android.systemui" content-desc="" clickable="false" bounds="[0,0][1080,2400]">
      <node index="0" text="19:15" resource-id="com.android.systemui:id/clock_view" class="android.widget.TextView" package="com.android.systemui" content-desc="" clickable="false" bounds="[0,0][1080,2400]" />
    </node>
    <node index="1" text="" resource-id="com.android.systemui:id/lock_icon" class="android.widget.ImageView" package="com.android.systemui" content-desc="Lock icon" clickable="true" bounds="[490,2100][590,2200]" />
  </node>
</hierarchy>"""


# ─────────────────────────────────────────────────────
# Helpers
# ─────────────────────────────────────────────────────


# ─────────────────────────────────────────────────────
# PERCEPTION TESTS
# ─────────────────────────────────────────────────────


# Removed mock_screen_memory fixture to allow real Qdrant database interactions


class TestSAEPerception:
    """Tests that the SAE correctly classifies screen situations."""

    def test_perceive_normal_instagram(self, make_real_device_with_image):
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        result = sae.perceive(INSTAGRAM_HOME_XML)
        assert result == SituationType.NORMAL

    def test_perceive_foreign_app_google(self, make_real_device_with_image):
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        result = sae.perceive(GOOGLE_SEARCH_XML)
        assert result == SituationType.OBSTACLE_FOREIGN_APP

    def test_perceive_system_permission_dialog(self, make_real_device_with_image):
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        result = sae.perceive(PERMISSION_DIALOG_XML)
        assert result == SituationType.OBSTACLE_SYSTEM

    def test_perceive_instagram_survey_modal(self, make_real_device_with_image):
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        result = sae.perceive(INSTAGRAM_SURVEY_XML)
        assert result == SituationType.OBSTACLE_MODAL

    def test_perceive_unknown_modal_interstitial(self, make_real_device_with_image):
        """SAE must detect modals it has NEVER seen before — no hardcoded IDs."""
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        sae.unlearn_current_state(UNKNOWN_MODAL_XML)
        result = sae.perceive(UNKNOWN_MODAL_XML)
        assert result == SituationType.OBSTACLE_MODAL

    def test_perceive_action_blocked(self, make_real_device_with_image):
        blocked_xml = INSTAGRAM_HOME_XML.replace(
            'text="" resource-id="com.instagram.android:id/feed_tab"',
            'text="Try again later" resource-id="com.instagram.android:id/bottom_sheet_container"',
        )
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        result = sae.perceive(blocked_xml)
        assert result == SituationType.DANGER_ACTION_BLOCKED

    def test_perceive_empty_dump(self, make_real_device_with_image):
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        result = sae.perceive("")
        assert result == SituationType.OBSTACLE_FOREIGN_APP

    def test_perceive_none_dump(self, make_real_device_with_image):
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        result = sae.perceive(None)
        assert result == SituationType.OBSTACLE_FOREIGN_APP

    def test_perceive_passive_scaffold_as_normal(self, make_real_device_with_image):
        """Passive scaffold containers (bottom_sheet_container_view, bottom_sheet_camera_container) must NOT be OBSTACLE_MODAL."""
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)

        # XML containing navigation tabs + the passive scaffold container
        passive_xml = INSTAGRAM_HOME_XML.replace(
            '<node index="1" text="" resource-id="com.instagram.android:id/main_feed_container"',
            '<node index="1" text="" resource-id="com.instagram.android:id/bottom_sheet_container_view" />\n'
            '<node index="2" text="" resource-id="com.instagram.android:id/bottom_sheet_camera_container" />\n'
            '<node index="3" text="" resource-id="com.instagram.android:id/main_feed_container"',
        )
        result = sae.perceive(passive_xml)
        assert result == SituationType.NORMAL, f"Passive scaffold misclassified as {result}"


# ─────────────────────────────────────────────────────
# REAL FIXTURE PERCEPTION TESTS (Phase 3)
# Validates structural fast-checks against production XML
# ─────────────────────────────────────────────────────

FIXTURE_DIR = os.path.join(os.path.dirname(__file__), "fixtures")


def _load_fixture(name: str) -> str:
    """Load a real XML fixture file."""
    path = os.path.join(FIXTURE_DIR, name)
    with open(path, "r") as f:
        return f.read()


class TestSAERealFixturePerception:
    """Tests perceive() against REAL production XML dumps to prevent false-positive obstacles."""

    def test_perceive_home_feed_as_normal(self, make_real_device_with_image):
        """Real home feed XML (with ads, stories tray) must be NORMAL — zero LLM calls."""
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        xml = _load_fixture("home_feed_real.xml")
        result = sae.perceive(xml)
        assert result == SituationType.NORMAL, f"Home feed misclassified as {result}"

    def test_perceive_explore_grid_as_normal(self, make_real_device_with_image):
        """Real explore grid XML must be NORMAL — zero LLM calls."""
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        xml = _load_fixture("explore_grid_real.xml")
        result = sae.perceive(xml)
        assert result == SituationType.NORMAL, f"Explore grid misclassified as {result}"

    def test_perceive_other_profile_as_normal(self, make_real_device_with_image):
        """Real other-user profile XML must be NORMAL — zero LLM calls."""
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        xml = _load_fixture("other_profile_real.xml")
        result = sae.perceive(xml)
        assert result == SituationType.NORMAL, f"Other profile misclassified as {result}"

    def test_perceive_post_detail_as_normal(self, make_real_device_with_image):
        """Real post detail XML must be NORMAL — zero LLM calls."""
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        xml = _load_fixture("post_detail_real.xml")
        result = sae.perceive(xml)
        assert result == SituationType.NORMAL, f"Post detail misclassified as {result}"

    def test_perceive_profile_tagged_tab_as_normal(self, make_real_device_with_image):
        """Real profile tagged-tab XML must be NORMAL — zero LLM calls."""
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        xml = _load_fixture("profile_tagged_tab.xml")
        result = sae.perceive(xml)
        assert result == SituationType.NORMAL, f"Profile tagged tab misclassified as {result}"

    def test_perceive_survey_modal_as_obstacle(self, make_real_device_with_image):
        """Inline survey modal XML (with survey_overlay_container) must be OBSTACLE_MODAL."""
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        result = sae.perceive(INSTAGRAM_SURVEY_XML)
        assert result == SituationType.OBSTACLE_MODAL, f"Survey modal misclassified as {result}"

    def test_perceive_mystery_interstitial_as_obstacle(self, make_real_device_with_image):
        """Inline interstitial modal XML must be OBSTACLE_MODAL."""
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        sae.unlearn_current_state(UNKNOWN_MODAL_XML)
        result = sae.perceive(UNKNOWN_MODAL_XML)
        assert result == SituationType.OBSTACLE_MODAL, f"Mystery interstitial misclassified as {result}"


# ─────────────────────────────────────────────────────
# Lying mock tests for Autonomous Recovery and Learning
# (TestSAEAutonomousRecovery, TestSAELearning) have been purged.
# ─────────────────────────────────────────────────────


# Autonomous Recovery and Learning tests were removed because they used
# StatefulMockDevice with string transitions — pure theater.
# Real coverage for this path requires a GoalExecutor.achieve() E2E test
# with XML fixture sequences simulating obstacle encounters.


# ─────────────────────────────────────────────────────
# STORY VIEW DETECTION TESTS (Phase 4)
# Exposes critical gap: Story screens were classified as UNKNOWN,
# causing GOAP to scroll blindly instead of pressing back.
# ─────────────────────────────────────────────────────


class TestScreenIdentityRealFixtures:
    """ScreenIdentity must accurately parse standard screens and extract all valid available_actions.
    No LLM fallback should be necessary to know that the home tab exists on the home feed.

    Bug evidence from run 2026-04-27_23-46-57:
    - Bot started on a Story screen (reel_viewer_media_layout, Like Story button)
    - ScreenIdentity returned UNKNOWN
    - GOAP chose 'scroll down' 4 times instead of 'press back'
    - Bot was trapped in an infinite scroll loop on a story
    """

    def test_screen_identity_parses_home_feed_actions(self):
        from GramAddict.core.perception.screen_identity import ScreenIdentity

        si = ScreenIdentity(bot_username="marisaundmarc")
        xml = _load_fixture("home_feed_real.xml")
        result = si.identify(xml)
        assert len(result["available_actions"]) > 0, "No actions parsed for Home Feed!"
        assert "tap explore tab" in result["available_actions"]
        assert "tap profile tab" in result["available_actions"]

    def test_screen_identity_parses_explore_grid_actions(self):
        from GramAddict.core.perception.screen_identity import ScreenIdentity

        si = ScreenIdentity(bot_username="marisaundmarc")
        xml = _load_fixture("explore_grid_real.xml")
        result = si.identify(xml)
        assert len(result["available_actions"]) > 0, "No actions parsed for Explore Grid!"
        assert "tap home tab" in result["available_actions"]

    def test_screen_identity_parses_other_profile_actions(self):
        from GramAddict.core.perception.screen_identity import ScreenIdentity

        si = ScreenIdentity(bot_username="marisaundmarc")
        xml = _load_fixture("other_profile_real.xml")
        result = si.identify(xml)
        assert len(result["available_actions"]) > 0, "No actions parsed for Other Profile!"
        assert "tap back button" in result["available_actions"]

    def test_screen_identity_parses_post_detail_actions(self):
        from GramAddict.core.perception.screen_identity import ScreenIdentity

        si = ScreenIdentity(bot_username="marisaundmarc")
        xml = _load_fixture("post_detail_real.xml")
        result = si.identify(xml)
        assert len(result["available_actions"]) > 0, "No actions parsed for Post Detail!"
        assert "press back" in result["available_actions"]

    def test_screen_identity_classifies_story_as_story_view(self):
        """ScreenIdentity must detect reel_viewer_* markers as STORY_VIEW."""
        from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType

        si = ScreenIdentity(bot_username="marisaundmarc")
        xml = _load_fixture("story_view_full.xml")
        result = si.identify(xml)

        assert result["screen_type"] == ScreenType.STORY_VIEW, (
            f"Story view misclassified as {result['screen_type']}! "
            f"Expected STORY_VIEW but ScreenIdentity returned {result['screen_type'].name}."
        )

    def test_sae_perceive_story_as_normal(self, make_real_device_with_image):
        """SAE must classify Story views as NORMAL (it's Instagram, not an obstacle).

        The bot's reaction to a Story should be: press back → navigate away.
        But first, SAE must NOT flag it as an obstacle.
        """
        device = make_real_device_with_image(None, "")
        sae = SituationalAwarenessEngine(device)
        xml = _load_fixture("story_view_full.xml")
        result = sae.perceive(xml)
        assert result == SituationType.NORMAL, f"Story view misclassified as {result}"

    def test_story_view_available_actions_include_press_back(self):
        """On a story, 'press back' must be in available actions and 'scroll down' should NOT
        be a meaningful action (stories don't scroll, they swipe)."""
        from GramAddict.core.perception.screen_identity import ScreenIdentity

        si = ScreenIdentity(bot_username="marisaundmarc")
        xml = _load_fixture("story_view_full.xml")
        result = si.identify(xml)

        assert "press back" in result["available_actions"], "'press back' must be available on Story views!"

    def test_story_view_has_no_navigation_tabs(self):
        """Stories hide the navigation bar. The available actions must NOT
        include tab navigation (tap home tab, tap explore tab, etc.)."""
        from GramAddict.core.perception.screen_identity import ScreenIdentity

        si = ScreenIdentity(bot_username="marisaundmarc")
        xml = _load_fixture("story_view_full.xml")
        result = si.identify(xml)

        tab_actions = [a for a in result["available_actions"] if "tap" in a and "tab" in a]
        assert len(tab_actions) == 0, f"Story view should have NO tab navigation, but found: {tab_actions}"
