from unittest.mock import MagicMock

from GramAddict.core.goap import GoalExecutor, ScreenType


def test_null_action_escalates_to_trap():
    """
    TDD Test: Verify that when an action is executed but the screen state does not change
    (null-action / dead button), the GOAP loop tracks the failure and eventually burns
    the action as a trap to prevent infinite loops.
    """
    device_mock = MagicMock()
    # Mock dump_hierarchy to simulate no UI change
    device_mock.dump_hierarchy.return_value = "<hierarchy></hierarchy>"

    nav = GoalExecutor(device=device_mock, bot_username="test_user")

    # Mock perceive to always return the SAME screen (EXPLORE_GRID)
    nav.perceive = MagicMock(
        return_value={"screen_type": ScreenType.EXPLORE_GRID, "available_actions": ["tap broken button"]}
    )

    # Mock _execute_action to return False (which is what happens when ui_changed is False)
    nav._execute_action = MagicMock(return_value=False)

    # Mock plan_next_step to repeatedly suggest the same action
    nav.planner.plan_next_step = MagicMock(return_value="tap broken button")

    # Mock learn_trap to verify it gets called
    nav.planner.knowledge.learn_trap = MagicMock()

    # Run a short loop (max 3 steps)
    nav.achieve(goal="open profile", max_steps=3)

    # Verify that the action was executed multiple times
    assert nav._execute_action.call_count == 3

    # The action failed on step 1 -> fail count = 1
    # The action failed on step 2 -> fail count = 2
    # At fail count 2, learn_trap MUST be called.
    nav.planner.knowledge.learn_trap.assert_called_with(
        ScreenType.EXPLORE_GRID, "tap broken button", "repeated_failure_or_null_action"
    )
