from unittest.mock import MagicMock

from GramAddict.core.goap import GoalExecutor, ScreenType


def test_goal_executor_masks_failed_actions(monkeypatch):
    """
    TDD Test: Verifiziert, dass der GoalExecutor eine Aktion, die mehrmals
    fehlschlägt, temporär aus den available_actions entfernt, um Loops zu verhindern.
    """
    device = MagicMock()
    executor = GoalExecutor(device, "test_user")

    # Mock perceive so we always return a static screen that has 'tap follow button' available.
    MagicMock()

    def fake_perceive(*args, **kwargs):
        # We must return a NEW dict each time so masking doesn't permanently modify the mock's template
        return {
            "screen_type": ScreenType.OWN_PROFILE,
            "available_actions": ["tap follow button", "press back"],
            "context": {},
        }

    executor.perceive = MagicMock(side_effect=fake_perceive)

    # Original planner behavior or mock:
    # 'plan_next_step' naturally suggests 'tap follow button' if 'follow' is in goal.
    # We will just verify the raw call to _execute_action.

    # We mock _execute_action to ALWAYS fail for 'tap follow button',
    # and if 'press back' is called, we return True and artificially complete the goal.
    executor.execute_calls = []

    def fake_execute(action, **kwargs):
        executor.execute_calls.append(action)
        if action == "tap follow button":
            return False
        if action == "press back":
            # Simulated exit to end the loop
            executor.goal_achieved = True
            return True
        return False

    monkeypatch.setattr(executor, "_execute_action", fake_execute)

    # Modify the loop so it breaks if goal_achieved is set
    original_plan = executor.planner.plan_next_step

    def hooked_plan(goal, screen, *args, **kwargs):
        if getattr(executor, "goal_achieved", False):
            return None  # Stop GOAP
        return original_plan(goal, screen, *args, **kwargs)

    executor.planner.plan_next_step = MagicMock(side_effect=hooked_plan)

    # Speed up sleep in the loop
    monkeypatch.setattr("GramAddict.core.goap.random_sleep", lambda x, y: None)

    # Set max_steps
    executor.max_steps = 10

    # Mock PathMemory to avoid real DB access which adds a recall attempt
    executor.path_memory.recall_path = MagicMock(return_value=[])

    # Execute
    executor.achieve("follow user")

    # Ohne Loop-Prevention würde execute_calls 10 mal 'tap follow button' enthalten
    # Mit Loop-Prevention sollte er <= 2 mal 'tap follow button' versuchen, dann es maskieren,
    # und dann den Fallback ('press back') versuchen, was then finishes the goal.
    count_follow = executor.execute_calls.count("tap follow button")

    assert (
        count_follow <= 2
    ), f"GoalExecutor ist in einem Loop gefangen! Versuchte die fehlgeschlagene Aktion {count_follow} mal anstatt sie zu maskieren."
