"""
TDD: Structural Dismiss Target Discovery

Proves that the SAE finds dismiss/close buttons by scanning
the XML structurally — zero hardcoded coordinates.
All coordinates come from bounds attributes in the XML itself.
"""

from GramAddict.core.situational_awareness import (
    SituationalAwarenessEngine,
)


class DummyDeviceV2:
    def __init__(self):
        self.info = {"screenOn": True}


class DummyDevice:
    def __init__(self):
        self.deviceV2 = DummyDeviceV2()
        self.app_id = "com.instagram.android"
        self.pressed = []
        self.clicks = []

    def press(self, key):
        self.pressed.append(key)

    def click(self, x, y):
        self.clicks.append((x, y))


# ── XML Fixtures ──


BOTTOM_SHEET_WITH_CANCEL = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" package="com.instagram.android"
        resource-id="com.instagram.android:id/bottom_sheet_container"
        class="android.widget.FrameLayout" clickable="false"
        bounds="[0,800][1080,2424]">
    <node index="0" text="Mute" clickable="true" bounds="[100,900][980,1000]" />
    <node index="1" text="Restrict" clickable="true" bounds="[100,1010][980,1110]" />
    <node index="2" text="Block" clickable="true" bounds="[100,1120][980,1220]" />
    <node index="3" text="Cancel" clickable="true" bounds="[100,1400][980,1500]" />
  </node>
</hierarchy>
"""

BOTTOM_SHEET_WITH_CLOSE_DESC = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" package="com.instagram.android"
        resource-id="com.instagram.android:id/action_sheet_container"
        class="android.widget.FrameLayout" clickable="false"
        bounds="[0,600][1080,2424]">
    <node index="0" text="" content-desc="Close" clickable="true" bounds="[900,620][980,700]" />
    <node index="1" text="Share to..." clickable="true" bounds="[100,800][980,900]" />
    <node index="2" text="Copy Link" clickable="true" bounds="[100,910][980,1010]" />
  </node>
</hierarchy>
"""

BOTTOM_SHEET_NO_DISMISS = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" package="com.instagram.android"
        resource-id="com.instagram.android:id/bottom_sheet_container"
        class="android.widget.FrameLayout" clickable="false"
        bounds="[0,800][1080,2424]">
    <node index="0" text="Mute" clickable="true" bounds="[100,900][980,1000]" />
    <node index="1" text="Restrict" clickable="true" bounds="[100,1010][980,1110]" />
    <node index="2" text="Block" clickable="true" bounds="[100,1120][980,1220]" />
  </node>
</hierarchy>
"""

NOT_NOW_DIALOG = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" package="com.instagram.android"
        resource-id="com.instagram.android:id/survey_overlay_container"
        class="android.widget.FrameLayout" clickable="false"
        bounds="[0,0][1080,2424]">
    <node index="0" text="Turn on Notifications?" clickable="false" bounds="[100,800][980,900]" />
    <node index="1" text="Turn On" clickable="true" bounds="[100,950][980,1050]" />
    <node index="2" text="Not Now" clickable="true" bounds="[100,1060][980,1160]" />
  </node>
</hierarchy>
"""

GERMAN_DISMISS_DIALOG = """<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<hierarchy rotation="0">
  <node index="0" package="com.instagram.android"
        resource-id="com.instagram.android:id/interstitial_container"
        class="android.widget.FrameLayout" clickable="false"
        bounds="[0,0][1080,2424]">
    <node index="0" text="Benachrichtigungen aktivieren?" clickable="false" bounds="[100,800][980,900]" />
    <node index="1" text="Aktivieren" clickable="true" bounds="[100,950][980,1050]" />
    <node index="2" text="Nicht jetzt" clickable="true" bounds="[100,1060][980,1160]" />
  </node>
</hierarchy>
"""


# ── Tests ──


class TestStructuralDismissTarget:
    """Verify that _find_structural_dismiss_target discovers buttons from XML, not hardcoded values."""

    def _get_sae(self):
        device = DummyDevice()
        return SituationalAwarenessEngine.get_instance(device)

    def test_finds_cancel_button_from_xml(self):
        """RED: structural scan must find 'Cancel' button with XML-derived coordinates."""
        sae = self._get_sae()
        result = sae._find_structural_dismiss_target(BOTTOM_SHEET_WITH_CANCEL)

        assert result is not None, "Should have found the Cancel button"
        assert result.action_type == "click"
        # Coordinates must come from bounds="[100,1400][980,1500]" → center (540, 1450)
        assert result.x == 540
        assert result.y == 1450
        assert "cancel" in result.reason.lower()

    def test_finds_close_via_content_desc(self):
        """RED: structural scan must find 'Close' button via content-desc attribute."""
        sae = self._get_sae()
        result = sae._find_structural_dismiss_target(BOTTOM_SHEET_WITH_CLOSE_DESC)

        assert result is not None, "Should have found the Close button via content-desc"
        assert result.action_type == "click"
        # bounds="[900,620][980,700]" → center (940, 660)
        assert result.x == 940
        assert result.y == 660
        assert "close" in result.reason.lower()

    def test_returns_none_when_no_dismiss_exists(self):
        """RED: if the XML has no dismiss-type button, return None (don't invent one)."""
        sae = self._get_sae()
        result = sae._find_structural_dismiss_target(BOTTOM_SHEET_NO_DISMISS)

        assert result is None, "Must not invent dismiss targets when none exist in XML"

    def test_finds_not_now_button(self):
        """RED: structural scan must find 'Not Now' button."""
        sae = self._get_sae()
        result = sae._find_structural_dismiss_target(NOT_NOW_DIALOG)

        assert result is not None, "Should have found 'Not Now' button"
        assert result.action_type == "click"
        # bounds="[100,1060][980,1160]" → center (540, 1110)
        assert result.x == 540
        assert result.y == 1110

    def test_finds_german_nicht_jetzt(self):
        """RED: structural scan must find German 'Nicht jetzt' button."""
        sae = self._get_sae()
        result = sae._find_structural_dismiss_target(GERMAN_DISMISS_DIALOG)

        assert result is not None, "Should have found 'Nicht jetzt' button"
        assert result.action_type == "click"
        # bounds="[100,1060][980,1160]" → center (540, 1110)
        assert result.x == 540
        assert result.y == 1110

    def test_no_hardcoded_coordinates_in_method(self):
        """
        META-TEST: Prove that _find_structural_dismiss_target contains
        zero hardcoded pixel values. All coordinates must come from XML parsing.
        """
        import inspect

        source = inspect.getsource(SituationalAwarenessEngine._find_structural_dismiss_target)

        # Should NOT contain any hardcoded coordinate patterns like x=540, y=150, etc.
        # The only numbers allowed are in the regex patterns, not in EscapeAction constructors
        import re

        # Find all EscapeAction instantiations with literal x= or y= values
        hardcoded_coords = re.findall(r"EscapeAction\([^)]*(?:x=\d+|y=\d+)", source)
        assert len(hardcoded_coords) == 0, (
            f"Found hardcoded coordinates in _find_structural_dismiss_target: {hardcoded_coords}. "
            "All coordinates must come from XML bounds parsing."
        )


class TestLLMFallbackNoHardcoding:
    """Verify that _plan_escape_via_llm does NOT use hardcoded fallback coordinates."""

    def test_no_hardcoded_coordinates_in_llm_planner(self):
        """
        META-TEST: Prove that _plan_escape_via_llm contains zero hardcoded
        pixel coordinates as fallback values. When the LLM repeats, it must
        delegate to _find_structural_dismiss_target (which reads from XML).
        """
        import inspect
        import re

        source = inspect.getsource(SituationalAwarenessEngine._plan_escape_via_llm)

        # Find all EscapeAction("click", x=<number>, y=<number>) with hardcoded coords
        hardcoded = re.findall(r'EscapeAction\(\s*"click"\s*,\s*x=\d+\s*,\s*y=\d+', source)
        assert len(hardcoded) == 0, (
            f"Found hardcoded click coordinates in _plan_escape_via_llm: {hardcoded}. "
            "The bot must discover coordinates autonomously from the XML."
        )
