from unittest.mock import MagicMock, patch

import pytest

from GramAddict.core.q_nav_graph import QNavGraph


@pytest.fixture
def mock_device():
    device = MagicMock()
    device.app_id = "com.instagram.android"
    device.deviceV2 = MagicMock()
    # Mock current app to be Instagram
    device._get_current_app.return_value = "com.instagram.android"
    return device


def test_recovery_from_dm_view(mock_device):
    """
    Test Case: Bot starts in a deep softlock (UNKNOWN state).
    It wants to go to ReelsFeed.
    GOAP will try 'press back' heuristics but we simulate that they fail to change the screen.
    After 15 failed steps, QNavGraph should trigger a hard recovery (app restart).
    """
    nav = QNavGraph(mock_device)
    nav.current_state = "UNKNOWN"

    valid_prefix = '<hierarchy><node package="com.instagram.android">'
    valid_suffix = "</node></hierarchy>"

    dm_xml = f'{valid_prefix}<node resource-id="message_input" />{valid_suffix}'
    home_xml = f'{valid_prefix}<node resource-id="feed_tab" selected="true" /><node resource-id="clips_tab" clickable="true" bounds="[0,0][100,100]" />{valid_suffix}'
    reels_xml = f'{valid_prefix}<node resource-id="clips_tab" selected="true" />{valid_suffix}'

    call_counts = {"dumps": 0}

    def custom_dump(*args, **kwargs):
        call_counts["dumps"] += 1

        # If app_start hasn't been called, we are still locked in the DM screen
        if not mock_device.app_start.called:
            return dm_xml
        else:
            # After forced app_start, we land on Home.
            # If a click happened since app_start, we assume it was the 'tap reels tab'
            if mock_device.click.called:
                return reels_xml
            return home_xml

    mock_device.dump_hierarchy.side_effect = custom_dump

    zero_engine = MagicMock()
    with (
        patch("GramAddict.core.telepathic_engine.TelepathicEngine.get_instance") as mock_get,
        patch("time.sleep"),
        patch("GramAddict.core.goap.random_sleep"),
        patch("GramAddict.core.utils.random_sleep"),
    ):  # Patch BOTH random_sleeps
        mock_engine = MagicMock()
        mock_get.return_value = mock_engine

        def mock_find(xml, desc, device=None, **kwargs):
            # In DM screen, nothing constructive is found
            if "message_input" in xml:
                return None
            # On Home screen, we find the tab
            return {"x": 50, "y": 50, "score": 0.95, "source": "keyword"}

        mock_engine.find_best_node.side_effect = mock_find

        # This should trigger recovery after 15 GOAP steps
        success = nav.navigate_to("ReelsFeed", zero_engine)

        assert success is True
        assert nav.current_state == "ReelsFeed"
        # Verify hard recovery was triggered
        mock_device.app_start.assert_called_with("com.instagram.android", use_monkey=True)
        # 15 perception dumps + 15 execute dumps + verified dumps + retry dumps
        assert call_counts["dumps"] >= 16
