"""
TDD: Close Friends Guard & ContextMemory Circuit Breaker

1. Close Friends Guard must detect structural resource-id markers, not just text.
2. ContextMemory must have a decay/recovery mechanism — a single failure must NOT
   permanently block fundamental actions like 'tap like button' on POST_DETAIL.
3. The circuit breaker threshold must be LOWER for core actions.
"""

import time

import pytest

from GramAddict.core.behaviors import BehaviorContext, BehaviorResult
from GramAddict.core.behaviors.close_friends_guard import CloseFriendsGuardPlugin


# ── Fixtures ──


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 = []
        self._display_info = {"width": 1080, "height": 2424}

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

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


# Instagram shows close friends via structural markers, not text
FEED_WITH_CLOSE_FRIEND_POST = """<?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/coordinator_root_layout"
        class="android.view.ViewGroup" clickable="false" bounds="[0,0][1080,2424]">
    <node index="0" resource-id="com.instagram.android:id/friendly_bubbles_component"
          class="android.view.ViewGroup" clickable="false" bounds="[42,1761][927,1925]" />
    <node index="1" text="Friends" content-desc="Friends"
          class="android.widget.LinearLayout" clickable="true" bounds="[491,213][858,302]" />
    <node index="2" text="" resource-id="com.instagram.android:id/row_feed_photo_profile_name"
          class="android.widget.TextView" clickable="true" bounds="[100,300][400,350]" />
  </node>
</hierarchy>
"""

FEED_NORMAL_POST = """<?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/coordinator_root_layout"
        class="android.view.ViewGroup" clickable="false" bounds="[0,0][1080,2424]">
    <node index="0" text="" resource-id="com.instagram.android:id/row_feed_photo_profile_name"
          class="android.widget.TextView" clickable="true" bounds="[100,300][400,350]" />
  </node>
</hierarchy>
"""

PROFILE_WITH_CLOSE_FRIEND_BADGE = """<?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/coordinator_root_layout"
        class="android.view.ViewGroup" clickable="false" bounds="[0,0][1080,2424]">
    <node index="0" resource-id="com.instagram.android:id/profile_header_close_friend"
          class="android.view.View" clickable="false" bounds="[0,0][100,100]" />
    <node index="1" text="username" resource-id="com.instagram.android:id/action_bar_title"
          class="android.widget.TextView" clickable="false" bounds="[200,50][500,100]" />
  </node>
</hierarchy>
"""

REELS_WITH_GREEN_STAR = """<?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/clips_viewer_view_pager"
        class="android.view.ViewGroup" clickable="false" bounds="[0,0][1080,2424]">
    <node index="0" resource-id="com.instagram.android:id/close_friends_badge"
          class="android.view.View" clickable="false" bounds="[50,300][90,340]"
          content-desc="Close friends" />
    <node index="1" text="username" class="android.widget.TextView"
          clickable="true" bounds="[100,310][400,350]" />
  </node>
</hierarchy>
"""


# ── Tests: Close Friends Guard ──


class TestCloseFriendsGuard:
    """The guard must detect close friends via STRUCTURAL markers, not just text."""

    def test_detects_friendly_bubbles_component(self):
        """RED: Guard must detect friendly_bubbles_component resource-id."""
        plugin = CloseFriendsGuardPlugin()
        ctx = BehaviorContext(
            device=DummyDevice(),
            session_state=type("S", (), {"job_target": "test"})(),
            shared_state={},
            context_xml=FEED_WITH_CLOSE_FRIEND_POST,
            configs=None,
            cognitive_stack=None,
        )
        assert plugin.can_activate(ctx), (
            "CloseFriendsGuard must detect 'friendly_bubbles_component' as a close friend indicator"
        )

    def test_does_not_fire_on_normal_post(self):
        """Guard must NOT fire on normal posts without close friend markers."""
        plugin = CloseFriendsGuardPlugin()
        ctx = BehaviorContext(
            device=DummyDevice(),
            session_state=type("S", (), {"job_target": "test"})(),
            shared_state={},
            context_xml=FEED_NORMAL_POST,
            configs=None,
            cognitive_stack=None,
        )
        assert not plugin.can_activate(ctx), "Guard must not fire on normal posts"

    def test_detects_profile_header_close_friend(self):
        """RED: Guard must detect profile_header_close_friend resource-id on profiles."""
        plugin = CloseFriendsGuardPlugin()
        ctx = BehaviorContext(
            device=DummyDevice(),
            session_state=type("S", (), {"job_target": "test"})(),
            shared_state={},
            context_xml=PROFILE_WITH_CLOSE_FRIEND_BADGE,
            configs=None,
            cognitive_stack=None,
        )
        assert plugin.can_activate(ctx), (
            "CloseFriendsGuard must detect 'profile_header_close_friend' resource-id"
        )

    def test_detects_close_friends_badge_in_reels(self):
        """RED: Guard must detect close_friends_badge resource-id in Reels."""
        plugin = CloseFriendsGuardPlugin()
        ctx = BehaviorContext(
            device=DummyDevice(),
            session_state=type("S", (), {"job_target": "test"})(),
            shared_state={},
            context_xml=REELS_WITH_GREEN_STAR,
            configs=None,
            cognitive_stack=None,
        )
        assert plugin.can_activate(ctx), (
            "CloseFriendsGuard must detect 'close_friends_badge' resource-id in Reels"
        )

    def test_no_hardcoded_text_matching(self):
        """
        META-TEST: The guard must NOT rely solely on text matching.
        It must use structural resource-id markers.
        """
        import inspect
        source = inspect.getsource(CloseFriendsGuardPlugin.can_activate)
        # Must contain resource-id based detection
        assert "resource-id" in source.lower() or "resource_id" in source.lower() or \
               "friendly_bubbles" in source or "close_friends_badge" in source or \
               "profile_header_close_friend" in source, (
            "can_activate must use structural resource-id markers, not just text matching"
        )


# ── Tests: ContextMemory Circuit Breaker ──


class TestContextMemoryCircuitBreaker:
    """
    The circuit breaker must NOT permanently block core actions.
    A single failure must decay over time, allowing re-exploration.
    """

    def test_circuit_breaker_has_time_decay(self):
        """
        META-TEST: ContextMemory.is_allowed must consider time since last update.
        Old failures (> 1 hour) must be forgiven to allow re-exploration.
        """
        import inspect
        from GramAddict.core.qdrant_memory import ContextMemoryDB

        source = inspect.getsource(ContextMemoryDB.is_allowed)
        # Must reference time-based decay or updated_at
        assert "updated_at" in source or "time" in source or "decay" in source or "age" in source, (
            "is_allowed must implement time-based decay. A single failure should not "
            "permanently block an action. Old failures must be forgiven."
        )

    def test_core_actions_have_lower_block_threshold(self):
        """
        META-TEST: Core navigation actions like 'tap like button' or 'tap home tab'
        must be harder to permanently block than obscure actions.
        """
        import inspect
        from GramAddict.core.qdrant_memory import ContextMemoryDB

        source = inspect.getsource(ContextMemoryDB.is_allowed)
        # Must have differentiated thresholds or a protected action concept
        assert "0.2" not in source or "core" in source.lower() or "protected" in source.lower() or \
               "updated_at" in source, (
            "The 0.2 hard threshold is too aggressive. Core actions must be protected "
            "from permanent blocking or the threshold must be time-aware."
        )
