from unittest.mock import MagicMock, patch

import pytest

from GramAddict.core.bot_flow import _run_zero_latency_feed_loop


def test_feed_markers_missing_prevents_back_button_trap():
    """
    TDD Test: When the bot is on the feed but no feed markers (like buttons) are visible
    (e.g., due to a tall image or mid-scroll), it must NOT press the Android 'back' button,
    because pressing back on the Home Feed forces a jump to the top of the feed and a refresh.
    It should only press back if it explicitly detects an obstacle (e.g., a bottom sheet).
    """
    device = MagicMock()
    # Return XML that has NO feed markers and NO obstacles
    device.dump_hierarchy.return_value = '<?xml version="1.0"?><hierarchy><node text="some tall post" /></hierarchy>'

    configs = MagicMock()
    configs.args.ignore_close_friends = False
    configs.args.carousel_percentage = 0
    configs.args.interaction_users_amount = "1"

    # We want to break the loop after one pass. We can patch _humanized_scroll to raise an Exception.
    class LoopBreak(Exception):
        pass

    with patch("GramAddict.core.bot_flow._humanized_scroll", side_effect=LoopBreak) as mock_scroll:
        with patch("GramAddict.core.bot_flow.sleep"):
            with patch("GramAddict.core.bot_flow.is_ad", return_value=False):
                with patch("GramAddict.core.bot_flow.TelepathicEngine") as MockEng:
                    MockEng.get_instance.return_value._extract_semantic_nodes.return_value = [
                        1
                    ]  # prevent zero-node crash

                    dopamine = MagicMock()
                    dopamine.is_app_session_over.return_value = False
                    dopamine.wants_to_doomscroll.return_value = False
                    cog_stack = {"dopamine": dopamine}

                    try:
                        zero_engine = MagicMock()
                        nav_graph = MagicMock()
                        session_state = MagicMock()
                        session_state.check_limit.return_value = [False, False]
                        _run_zero_latency_feed_loop(
                            device, zero_engine, nav_graph, configs, session_state, "home_feed", cog_stack
                        )
                    except LoopBreak:
                        pass

    # It must NOT press back, because it's just lost in the feed without explicit obstacles.
    try:
        device.press.assert_not_called()
    except AssertionError:
        pytest.fail(
            "Agent incorrectly pressed BACK when no obstacle was present. This triggers the scroll-to-top trap!"
        )
    mock_scroll.assert_called_once()


def test_explicit_obstacle_triggers_back_button():
    """
    TDD Test: When the bot detects an explicit obstacle (e.g., dialog_container),
    it MUST press the back button to try and dismiss it.
    """
    device = MagicMock()
    # Return XML that HAS an obstacle
    device.dump_hierarchy.return_value = (
        '<?xml version="1.0"?><hierarchy><node resource-id="dialog_container" /></hierarchy>'
    )

    configs = MagicMock()
    configs.args.ignore_close_friends = False

    class LoopBreak(Exception):
        pass

    with patch("GramAddict.core.bot_flow.sleep"):
        with patch("GramAddict.core.bot_flow.is_ad", return_value=False):
            with patch("GramAddict.core.bot_flow.TelepathicEngine") as MockEng:
                MockEng.get_instance.return_value._extract_semantic_nodes.return_value = [1]
                dopamine = MagicMock()
                dopamine.is_app_session_over.return_value = False
                dopamine.wants_to_doomscroll.return_value = False
                cog_stack = {"dopamine": dopamine}

                try:
                    zero_engine = MagicMock()
                    nav_graph = MagicMock()
                    session_state = MagicMock()
                    session_state.check_limit.return_value = [False, False]
                    # Make device.press raise LoopBreak so we can verify it was called and break the infinite loop
                    device.press.side_effect = LoopBreak
                    _run_zero_latency_feed_loop(
                        device, zero_engine, nav_graph, configs, session_state, "home_feed", cog_stack
                    )
                except LoopBreak:
                    pass

    # device.press("back") SHOULD be called
    device.press.assert_called_with("back")
