"""
TDD Tests: Profile Screen Identity Classification

Reproduces the EXACT production bug from 2026-05-05 where ScreenMemory (Qdrant)
poisoned OWN_PROFILE classification as OTHER_PROFILE, causing an infinite navigation loop.

The fix ensures:
1. profile_tab selected=true → OWN_PROFILE (absolute structural anchor)
2. profile_header markers → OWN vs OTHER (deterministic disambiguation)
3. Qdrant cache CANNOT override structurally-gated screen types
"""

from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType


def _make_identity():
    """Create ScreenIdentity with Qdrant disabled (no mock needed — constructor guards)."""
    si = ScreenIdentity.__new__(ScreenIdentity)
    si.bot_username = "testuser"
    si.screen_memory = None
    return si


class TestProfileClassificationInvariants:
    """
    These tests enforce absolute structural invariants that prevent
    Qdrant cache poisoning from ever breaking profile navigation again.
    """

    def test_profile_tab_selected_always_means_own_profile(self):
        """INVARIANT: profile_tab selected=true → OWN_PROFILE. No exceptions."""
        si = _make_identity()
        xml = """<?xml version="1.0" encoding="utf-8"?>
        <hierarchy>
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_tab"
                  selected="true" text="" content-desc="Profile" clickable="true" bounds="[864,2268][1080,2400]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/feed_tab"
                  selected="false" text="" content-desc="Home" clickable="true" bounds="[0,2268][216,2400]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header_container"
                  text="" content-desc="" clickable="false" bounds="[0,100][1080,600]" />
        </hierarchy>
        """
        result = si.identify(xml)
        assert result["screen_type"] == ScreenType.OWN_PROFILE

    def test_own_profile_with_edit_button_no_tab_selected(self):
        """OWN_PROFILE detected via edit_profile button even without selected tab."""
        si = _make_identity()
        xml = """<?xml version="1.0" encoding="utf-8"?>
        <hierarchy>
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header"
                  text="" content-desc="" clickable="false" bounds="[0,100][1080,600]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header_edit_profile_button"
                  text="" content-desc="Edit Profile" clickable="true" bounds="[50,500][1030,570]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_tab"
                  selected="false" text="" content-desc="Profile" clickable="true" bounds="[864,2268][1080,2400]" />
        </hierarchy>
        """
        result = si.identify(xml)
        assert result["screen_type"] == ScreenType.OWN_PROFILE

    def test_other_profile_with_follow_button(self):
        """OTHER_PROFILE detected via follow button."""
        si = _make_identity()
        xml = """<?xml version="1.0" encoding="utf-8"?>
        <hierarchy>
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header"
                  text="" content-desc="" clickable="false" bounds="[0,100][1080,600]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header_follow_button"
                  text="" content-desc="Follow" clickable="true" bounds="[50,500][1030,570]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header_message_button"
                  text="" content-desc="Message" clickable="true" bounds="[50,580][1030,650]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_tab"
                  selected="false" text="" content-desc="Profile" clickable="true" bounds="[864,2268][1080,2400]" />
        </hierarchy>
        """
        result = si.identify(xml)
        assert result["screen_type"] == ScreenType.OTHER_PROFILE

    def test_profile_header_without_markers_defaults_to_own_profile(self):
        """
        Edge case: profile_header exists but neither edit nor follow button.
        Must default to OWN_PROFILE (safer than letting Qdrant guess OTHER_PROFILE).
        """
        si = _make_identity()
        xml = """<?xml version="1.0" encoding="utf-8"?>
        <hierarchy>
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header"
                  text="" content-desc="" clickable="false" bounds="[0,100][1080,600]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_tab"
                  selected="false" text="" content-desc="Profile" clickable="true" bounds="[864,2268][1080,2400]" />
        </hierarchy>
        """
        result = si.identify(xml)
        assert (
            result["screen_type"] == ScreenType.OWN_PROFILE
        ), "Profile without markers must default to OWN_PROFILE, not fall to Qdrant"


class TestQdrantCachePoisoningGuard:
    """
    Reproduces the exact production failure: Qdrant cached a profile screen
    as OTHER_PROFILE, and every subsequent identification returned OTHER_PROFILE
    even when on OWN_PROFILE. This must be prevented architecturally.
    """

    def test_poisoned_qdrant_cache_cannot_override_own_profile(self, monkeypatch):
        """
        PRODUCTION BUG REPRODUCTION:
        1. Qdrant has a cached entry mapping this XML signature → OTHER_PROFILE
        2. The XML has profile_tab selected=true → should be OWN_PROFILE
        3. Qdrant MUST NOT override the structural fast-path
        """
        si = _make_identity()

        # Simulate a poisoned Qdrant cache by attaching a fake screen_memory
        class FakePoisonedCache:
            is_connected = True

            def get_screen_type(self, sig, similarity_threshold=0.95):
                return "OTHER_PROFILE"

            def purge_stale_screens(self):
                pass

            def store_screen(self, sig, t):
                pass

        si.screen_memory = FakePoisonedCache()

        xml = """<?xml version="1.0" encoding="utf-8"?>
        <hierarchy>
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_tab"
                  selected="true" text="" content-desc="Profile" clickable="true" bounds="[864,2268][1080,2400]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header"
                  text="" content-desc="" clickable="false" bounds="[0,100][1080,600]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header_edit_profile_button"
                  text="" content-desc="Edit Profile" clickable="true" bounds="[50,500][1030,570]" />
        </hierarchy>
        """
        result = si.identify(xml)
        assert (
            result["screen_type"] == ScreenType.OWN_PROFILE
        ), "Poisoned Qdrant cache returned OTHER_PROFILE but structural fast-path should override!"

    def test_poisoned_qdrant_cache_cannot_override_other_profile(self, monkeypatch):
        """Qdrant cached OWN_PROFILE but XML shows follow button → OTHER_PROFILE."""
        si = _make_identity()

        class FakePoisonedCache:
            is_connected = True

            def get_screen_type(self, sig, similarity_threshold=0.95):
                return "OWN_PROFILE"

            def purge_stale_screens(self):
                pass

            def store_screen(self, sig, t):
                pass

        si.screen_memory = FakePoisonedCache()

        xml = """<?xml version="1.0" encoding="utf-8"?>
        <hierarchy>
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header"
                  text="" content-desc="" clickable="false" bounds="[0,100][1080,600]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header_follow_button"
                  text="" content-desc="Follow" clickable="true" bounds="[50,500][1030,570]" />
            <node package="com.instagram.android" resource-id="com.instagram.android:id/profile_tab"
                  selected="false" text="" content-desc="Profile" clickable="true" bounds="[864,2268][1080,2400]" />
        </hierarchy>
        """
        result = si.identify(xml)
        assert (
            result["screen_type"] == ScreenType.OTHER_PROFILE
        ), "Poisoned Qdrant cache returned OWN_PROFILE but structural fast-path should override!"

    def test_qdrant_profile_cache_rejected_when_no_structural_match(self, monkeypatch):
        """If Qdrant says OTHER_PROFILE but no profile markers exist, it must be rejected."""
        si = _make_identity()

        class FakePoisonedCache:
            is_connected = True

            def get_screen_type(self, sig, similarity_threshold=0.95):
                return "OTHER_PROFILE"

            def purge_stale_screens(self):
                pass

            def store_screen(self, sig, t):
                pass

        si.screen_memory = FakePoisonedCache()

        xml = """<?xml version="1.0" encoding="utf-8"?>
        <hierarchy>
            <node package="com.instagram.android" resource-id="com.instagram.android:id/feed_tab"
                  selected="true" text="" content-desc="Home" clickable="true" bounds="[0,2268][216,2400]" />
        </hierarchy>
        """
        result = si.identify(xml)
        # Qdrant said OTHER_PROFILE, but feed_tab is selected → HOME_FEED
        # The cache for profile types must be rejected by _STRUCTURALLY_GATED_TYPES guard
        assert (
            result["screen_type"] != ScreenType.OTHER_PROFILE
        ), "Qdrant cache for OTHER_PROFILE must be rejected when no profile markers exist!"
