import logging
from unittest.mock import MagicMock, patch

from GramAddict.core.q_nav_graph import QNavGraph


def test_qnavgraph_same_state_navigation_bug():
    """
    Test that reproducing the bug where `navigate_to` to the CURRENT state
    triggers an unexpected `app_start()` restart due to `if not path:` treating `[]` as failure.
    """
    mock_device = MagicMock()
    mock_device.deviceV2 = MagicMock()
    # Mock search tab selected (ExploreFeed)
    mock_device.dump_hierarchy.return_value = '<hierarchy><node package="com.instagram.android" resource-id="com.instagram.android:id/search_tab" selected="true" /></hierarchy>'
    mock_device.dump_hierarchy.return_value = '<hierarchy><node package="com.instagram.android" resource-id="com.instagram.android:id/search_tab" selected="true" /></hierarchy>'

    with (
        patch("GramAddict.core.goap.GoalExecutor._instance", None),
        patch(
            "GramAddict.core.goap.ScreenIdentity._classify_screen",
            return_value=__import__("GramAddict.core.goap", fromlist=["ScreenType"]).ScreenType.EXPLORE_GRID,
        ),
        patch("GramAddict.core.goap.GoalPlanner.plan_next_step", return_value=None),
        patch("GramAddict.core.goap.PathMemory.recall_path", return_value=None),
        patch("GramAddict.core.goap.PathMemory.learn_path"),
        patch("GramAddict.core.q_nav_graph.random_sleep"),
        patch("GramAddict.core.goap.random_sleep"),
        patch("time.sleep"),
    ):
        graph = QNavGraph(mock_device)
        graph.current_state = "ExploreFeed"
        graph.navigate_to("ExploreFeed", zero_engine=None)
        mock_device.app_start.assert_not_called()


def test_qnavgraph_semantic_recovery_any_state():
    """
    Ensures that navigation from HomeFeed to ReelsFeed works via GOAP.
    """
    mock_device = MagicMock()
    # Mock sequence:
    # 1. Identify HomeFeed
    # 2. Click reels tab (pre-click)
    # 3. Click reels tab (post-click)
    mock_hierarchy = [
        '<hierarchy><node package="com.instagram.android" resource-id="com.instagram.android:id/home_tab" selected="true" /></hierarchy>',
        '<hierarchy><node package="com.instagram.android" resource-id="com.instagram.android:id/home_tab" selected="true" /><node package="com.instagram.android" resource-id="com.instagram.android:id/clips_tab" /></hierarchy>',
        '<hierarchy><node package="com.instagram.android" resource-id="com.instagram.android:id/clips_tab" selected="true" /></hierarchy>',
    ]
    mock_device.dump_hierarchy.side_effect = mock_hierarchy + [mock_hierarchy[-1]] * 10
    mock_device.dump_hierarchy.side_effect = mock_hierarchy + [mock_hierarchy[-1]] * 10

    graph = QNavGraph(mock_device)
    graph.current_state = "HomeFeed"

    mock_telepathic = MagicMock()
    mock_telepathic.find_best_node.return_value = {"x": 50, "y": 50, "score": 1.0, "source": "keyword", "skip": False}

    from GramAddict.core.goap import ScreenType

    with (
        patch("GramAddict.core.goap.GoalExecutor._instance", None),
        patch("GramAddict.core.telepathic_engine.TelepathicEngine.get_instance", return_value=mock_telepathic),
        patch(
            "GramAddict.core.goap.ScreenIdentity._classify_screen",
            side_effect=[
                ScreenType.HOME_FEED,
                ScreenType.HOME_FEED,
                ScreenType.REELS_FEED,
                ScreenType.REELS_FEED,
                ScreenType.REELS_FEED,
            ],
        ),
        patch("GramAddict.core.goap.GoalPlanner.plan_next_step", side_effect=["tap_reels_tab", None]),
        patch("GramAddict.core.goap.PathMemory.recall_path", return_value=None),
        patch("GramAddict.core.goap.PathMemory.learn_path"),
        patch("time.sleep"),
        patch("GramAddict.core.goap.random_sleep"),
    ):
        success = graph.navigate_to("ReelsFeed", zero_engine=None)

        assert success is True
        assert graph.current_state == "ReelsFeed"


def test_qnavgraph_telepathic_tagging(caplog):
    """
    Verifies that the transition logs correctly output the 'source' of the interaction
    (e.g. '[Keyword]' or '[Agentic Fallback]') instead of hardcoding '[Vision Cortex]' on a score of 1.0.
    """
    caplog.set_level(logging.INFO)
    mock_device = MagicMock()

    graph = QNavGraph(mock_device)

    # 1. Test Keyword Fast Path (Score 1.0)
    mock_hierarchy_1 = [
        '<hierarchy><node package="com.instagram.android" class="before" /></hierarchy>',
        '<hierarchy><node package="com.instagram.android" class="after" /></hierarchy>',
    ]
    mock_device.dump_hierarchy.side_effect = mock_hierarchy_1 + [mock_hierarchy_1[-1]] * 10
    mock_device.dump_hierarchy.side_effect = mock_hierarchy_1 + [mock_hierarchy_1[-1]] * 10
    mock_telepathic = MagicMock()
    mock_telepathic.find_best_node.return_value = {
        "x": 100,
        "y": 100,
        "score": 1.0,
        "semantic": "test match",
        "source": "keyword",
        "skip": False,
    }

    with patch("GramAddict.core.telepathic_engine.TelepathicEngine.get_instance", return_value=mock_telepathic):
        graph._execute_transition("tap_home_tab", None)
        assert "QNavGraph executing transition 'tap_home_tab' via [Keyword]" in caplog.text

    # 2. Test Agentic Fallback (Score < 1.0)
    caplog.clear()
    mock_hierarchy_2 = [
        '<hierarchy><node package="com.instagram.android" class="before" /></hierarchy>',
        '<hierarchy><node package="com.instagram.android" class="after" /></hierarchy>',
    ]
    mock_device.dump_hierarchy.side_effect = mock_hierarchy_2 + [mock_hierarchy_2[-1]] * 10
    mock_device.dump_hierarchy.side_effect = mock_hierarchy_2 + [mock_hierarchy_2[-1]] * 10
    mock_telepathic.find_best_node.return_value = {
        "x": 100,
        "y": 100,
        "score": 0.85,
        "semantic": "test LLM",
        "source": "agentic_fallback",
        "skip": False,
    }

    with patch("GramAddict.core.telepathic_engine.TelepathicEngine.get_instance", return_value=mock_telepathic):
        graph._execute_transition("tap_home_tab", None)
        assert "QNavGraph executing transition 'tap_home_tab' via [Agentic Fallback]" in caplog.text
