import os
from unittest.mock import MagicMock, patch

# Force mock qdrant_client before importing any core modules that depend on it
import pytest

from GramAddict.core.bot_flow import _run_zero_latency_feed_loop

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


class ArgsMock:
    def __init__(self):
        self.username = ["test_bot"]
        self.interact_percentage = 100
        self.likes_percentage = 100
        self.total_likes_limit = 100
        self.total_follows_limit = 100
        self.total_unfollows_limit = 100
        self.total_comments_limit = 100
        self.total_pm_limit = 100
        self.total_watches_limit = 100
        self.total_successful_interactions_limit = 100
        self.total_interactions_limit = 100
        self.total_scraped_limit = 100
        self.total_crashes_limit = 5
        # Session state attributes expect these to exist
        self.current_likes_limit = 100
        self.current_follow_limit = 100
        self.current_unfollow_limit = 100
        self.current_comments_limit = 100
        self.current_pm_limit = 100
        self.current_watch_limit = 100
        self.current_success_limit = 100
        self.current_total_limit = 100
        self.current_scraped_limit = 100
        self.current_crashes_limit = 5
        self.end_if_likes_limit_reached = False
        self.end_if_follows_limit_reached = False
        self.end_if_watches_limit_reached = False
        self.end_if_comments_limit_reached = False
        self.end_if_pm_limit_reached = False


class ConfigMock:
    def __init__(self):
        self.can_like = True
        self.can_comment = False
        self.can_follow = False
        self.can_watch_stories = False
        self.interaction_limit_reached = lambda: False
        self.is_last_post = lambda x: False
        self.args = ArgsMock()


@pytest.fixture
def fsd_fixtures():
    def _load(name):
        with open(os.path.join(FIX_DIR, name), "r") as f:
            return f.read()

    return {"organic": _load("organic_post.xml"), "ad": _load("sponsored_reel.xml"), "modal": _load("survey_modal.xml")}


def test_full_mission_autopilot_sequence(fsd_fixtures):
    """
    MASTER SCENARIO:
    1. Organic Post -> Action: LIKE
    2. Ad -> Action: SKIP (SCROLL)
    3. Survey Modal -> Action: DISMISS (TELEPATHIC)
    4. Organic Post -> Action: LIKE
    5. End Session (Boredom)
    """
    device = MagicMock()
    device.get_info.return_value = {"displayWidth": 1080, "displayHeight": 2400}
    configs = ConfigMock()

    # Sequence of UI states
    ui_sequence = [
        fsd_fixtures["organic"],  # 0. First Organic Post
        fsd_fixtures["ad"],  # 1. Ad (Detected via resource-id)
        fsd_fixtures["modal"]
        .replace("not_now_btn", "skip_survey_btn")
        .replace("Maybe Later", "Ignore"),  # 2. Modal (Miss 1)
        fsd_fixtures["modal"]
        .replace("not_now_btn", "skip_survey_btn")
        .replace("Maybe Later", "Ignore"),  # 3. Modal (Miss 2 -> Telepathic Recovery)
        fsd_fixtures["organic"],  # 4. Second Organic Post
        fsd_fixtures["organic"],  # Buffer
    ]
    state = {"index": 0}

    def get_ui():
        idx = min(state["index"], len(ui_sequence) - 1)
        return ui_sequence[idx]

    def advance_state(*args, **kwargs):
        state["index"] += 1
        print(f"DEBUG: State advanced to {state['index']}")

    device.dump_hierarchy.side_effect = get_ui
    device.dump_hierarchy.side_effect = get_ui
    device.click.side_effect = advance_state
    device.click.side_effect = advance_state
    device.app_id = "com.instagram.android"
    device._get_current_app.return_value = "com.instagram.android"
    device.app_is_running.return_value = True

    # Trackers
    class CRMTracker:
        def __init__(self):
            self.interacted_users = []

        def log_interaction(self, username, intent):
            print(f"DEBUG: CRM log_interaction called for @{username} with {intent}")
            self.interacted_users.append(username)

        def log_profile_context(self, *args, **kwargs):
            pass

    class DarwinTracker:
        def __init__(self):
            self.called = False

        def execute_micro_wobble(self, *args, **kwargs):
            pass

        def execute_proof_of_resonance(self, *args, **kwargs):
            self.called = True

        def synthesize_interaction_profile(self, *args, **kwargs):
            self.called = True

        def evaluate_session_end(self, *args, **kwargs):
            pass

    crm = CRMTracker()
    darwin = DarwinTracker()
    swarm = MagicMock()
    resonance = MagicMock()

    # Mock Resonance to always like organic posts
    resonance.calculate_resonance.return_value = 0.9

    # --- DETOX: Use REAL engines, mock only the BOUNDARY (LLM/DB) ---
    import builtins
    import hashlib

    from GramAddict.core.resonance_engine import ResonanceEngine
    from GramAddict.core.session_state import SessionState
    from GramAddict.core.swarm_protocol import SwarmProtocol
    from GramAddict.core.telepathic_engine import TelepathicEngine

    # Capture original open BEFORE any patching to avoid recursion
    original_open = builtins.open

    def deterministic_embedding(text):
        """Generates a stable, unique 1536-dim vector for any string."""
        # Use MD5 to get 16 bytes, then repeat to fill or just use first 16 floats
        h = hashlib.md5(text.encode()).digest()
        base = [float(b) / 255.0 for b in h]
        # Pad to 1536 with zeros or repeat
        return (base * (1536 // 16 + 1))[:1536]

    # We mock only the external API/Boundary calls inside the engines
    with (
        patch("GramAddict.core.qdrant_memory.QdrantClient") as MockClient,
        patch("GramAddict.core.qdrant_memory.QdrantBase._get_embedding", side_effect=deterministic_embedding),
        patch("GramAddict.core.telepathic_engine.query_telepathic_llm") as mock_vlm_api,
        patch("GramAddict.core.telepathic_engine.TelepathicEngine._cosine_similarity", return_value=0.1),
        patch(
            "GramAddict.core.bot_flow._extract_post_content",
            return_value={"username": "fiona.dawson", "description": "Organic post", "caption": ""},
        ),
        patch("GramAddict.core.bot_flow.sleep"),
        patch("GramAddict.core.bot_flow._humanized_scroll", side_effect=advance_state),
        patch("builtins.open", new_callable=MagicMock) as mock_file_open,
        patch("random.random", return_value=0.99),
    ):  # Pass interaction gates and bypass Resonance Skip
        # Setup fake file reading for VLM screenshot
        mock_file_open.return_value.__enter__.return_value.read.return_value = b"fake_screenshot_bytes"

        # We need to selectively mock open for 'vlm_context.jpg' and allow real open for XML fixtures
        def side_effect_open(path, *args, **kwargs):
            if "vlm_context.jpg" in str(path):
                return mock_file_open.return_value
            return original_open(path, *args, **kwargs)

        mock_file_open.side_effect = side_effect_open

        # Harden Qdrant Config Mock to prevent dimension warnings
        mock_client = MockClient.return_value
        mock_client.collection_exists.return_value = False

        # Force the REAL TelepathicEngine instead of conftest's MockTelepathicEngine
        telepathic = TelepathicEngine()

        # CLEAR MEMORY TO ENSURE VLM TRIGGER
        if os.path.exists("telepathic_memory.json"):
            os.remove("telepathic_memory.json")

        with patch("GramAddict.core.telepathic_engine.TelepathicEngine.get_instance", return_value=telepathic):
            resonance = ResonanceEngine(my_username="test_bot", persona_interests=["travel", "nature"])
            # Mock the specific method to always like organic posts, bypassing the deterministic embedding math
            resonance.calculate_resonance = MagicMock(return_value=0.9)
            swarm = SwarmProtocol(username="test_bot")

            cognitive_stack = {
                "active_inference": MagicMock(),
                "dopamine": MagicMock(),
                "growth_brain": MagicMock(),
                "resonance": resonance,
                "crm": crm,
                "swarm": swarm,
                "darwin": darwin,
                "telepathic": telepathic,
            }

            # Setup AI recovery (boundary mock result)
            # Viable nodes in survey_modal.xml are: 0: Take Survey, 1: Maybe Later
            mock_vlm_api.return_value = '{"index": 1, "reason": "Maybe Later Button"}'

            # Setup Dopamine to run exactly long enough
            cognitive_stack["dopamine"].is_app_session_over.side_effect = [False] * 12 + [True]
            cognitive_stack["dopamine"].wants_to_change_feed.return_value = False
            cognitive_stack["dopamine"].wants_to_doomscroll.return_value = False

            # Run interaction loop - we patch swarm's emit_pheromone to verify it was called
            with patch.object(swarm, "emit_pheromone"):
                session_state = SessionState(configs)
                _run_zero_latency_feed_loop(
                    device, MagicMock(), MagicMock(), configs, session_state, "HomeFeed", cognitive_stack
                )

                # VERIFICATION

                # 1. Sequence Progression
                assert state["index"] >= 4, f"Bot sequence failed to progress. Final index: {state['index']}"

                # 2. Interaction Accuracy (CRM)
                # Real ResonanceEngine should have evaluated 'hoeltlfinanzgmbh' as high resonance
                assert len(crm.interacted_users) >= 1, "CRM recorded ZERO interactions!"
                assert "fiona.dawson" in crm.interacted_users or "hoeltlfinanzgmbh" in crm.interacted_users

                # 3. Anomaly Handling
                # Real TelepathicEngine should have called the Vision LLM (mock_vlm_api)
                assert mock_vlm_api.called, "Anomaly recovery via REAL Vision Cortex was NEVER triggered!"

                # 4. Resonance Proof
                assert darwin.called, "Darwin Engine was NEVER called for resonance proof!"
                assert swarm.emit_pheromone.called, "Swarm Protocol NEVER emitted success pheromones!"

            print("\n🏆 TRUE INTEGRATION SCENARIO PASSED!")
            print(f"Interacted with: {crm.interacted_users}")


def test_feed_loop_chaos_mode(fsd_fixtures):
    """
    CHAOS MODE SCENARIO:
    Simulate unpredictable UI behavior, random context loss, and unhandled exceptions
    to ensure the zero-latency feed loop handles errors gracefully without crashing.
    """
    device = MagicMock()
    device.get_info.return_value = {"displayWidth": 1080, "displayHeight": 2400}

    class ConfigMock:
        def __init__(self):
            self.args = MagicMock()
            self.args.interact_percentage = 100
            self.args.likes_percentage = 100
            self.args.follow_percentage = 0
            self.args.comment_percentage = 0
            self.args.repost_percentage = 0

    configs = ConfigMock()

    # Sequence with invalid XML, completely empty hierarchy, then normal
    ui_sequence = ["INVALID XML {{", "<hierarchy></hierarchy>", fsd_fixtures["organic"]]
    state = {"index": 0}

    def get_ui():
        idx = min(state["index"], len(ui_sequence) - 1)
        return ui_sequence[idx]

    def advance_state(*args, **kwargs):
        state["index"] += 1

    device.dump_hierarchy.side_effect = get_ui
    device.click.side_effect = advance_state

    from GramAddict.core.sensors.honeypot_radome import HoneypotRadome

    with (
        patch("GramAddict.core.bot_flow.sleep"),
        patch("GramAddict.core.bot_flow._humanized_scroll", side_effect=advance_state),
    ):
        telepathic = MagicMock()
        # Have telepathic throw an error to simulate chaos/random failure
        telepathic._extract_semantic_nodes.side_effect = Exception("CHAOS INJECTION")

        cognitive_stack = {
            "active_inference": MagicMock(),
            "dopamine": MagicMock(),
            "growth_brain": MagicMock(),
            "resonance": MagicMock(),
            "crm": MagicMock(),
            "swarm": MagicMock(),
            "darwin": MagicMock(),
            "radome": HoneypotRadome(1080, 2400),
            "telepathic": telepathic,
            "nav_graph": MagicMock(),
            "zero_engine": MagicMock(),
        }
        cognitive_stack["resonance"].calculate_resonance.return_value = 0.9

        cognitive_stack["dopamine"].is_app_session_over.side_effect = [False, False, False, True]
        cognitive_stack["dopamine"].wants_to_change_feed.return_value = False
        cognitive_stack["dopamine"].wants_to_doomscroll.return_value = False

        session_state = MagicMock()
        session_state.check_limit.side_effect = (
            lambda limit_type: (False, False, False, False) if getattr(limit_type, "name", "") == "ALL" else False
        )

        from GramAddict.core.bot_flow import _run_zero_latency_feed_loop

        # The loop should not crash despite exceptions (e.g. invalid XML or CHAOS exception from telepathic)
        # Instead, it should catch exceptions and use _humanized_scroll or abort safely
        _run_zero_latency_feed_loop(
            device,
            cognitive_stack["zero_engine"],
            cognitive_stack["nav_graph"],
            configs,
            session_state,
            "HomeFeed",
            cognitive_stack,
        )

        # Should have advanced through the states via fallback scroll mechanism
        assert state["index"] >= 1
