from unittest.mock import MagicMock, create_autospec, patch

import pytest

from GramAddict.core.behaviors import BehaviorContext
from GramAddict.core.behaviors.obstacle_guard import ObstacleGuardPlugin
from GramAddict.core.situational_awareness import SituationType


@pytest.fixture
def obstacle_guard():
    plugin = ObstacleGuardPlugin()
    return plugin


@pytest.fixture
def mock_context():
    device = MagicMock()
    configs = MagicMock()
    session_state = MagicMock()

    ctx = BehaviorContext(device=device, configs=configs, session_state=session_state, cognitive_stack={})
    return ctx


class TestObstacleGuardPlugin:
    def test_can_activate(self, obstacle_guard, mock_context):
        assert obstacle_guard.can_activate(mock_context) is True

    @patch("GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine")
    def test_execute_no_obstacle_has_markers(self, mock_sae, obstacle_guard, mock_context):
        mock_instance = MagicMock()
        mock_instance.perceive.return_value = SituationType.NORMAL
        mock_sae.get_instance.return_value = mock_instance

        # Give context xml that has feed markers
        mock_context.device.dump_hierarchy.return_value = "<xml>dummy row_feed_button_like</xml>"
        mock_context.shared_state["consecutive_marker_misses"] = 2

        result = obstacle_guard.execute(mock_context)

        assert result.executed is False
        assert mock_context.shared_state["consecutive_marker_misses"] == 0

    @patch("GramAddict.core.behaviors.obstacle_guard.humanized_scroll")
    @patch("GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine")
    def test_execute_no_obstacle_no_markers(self, mock_sae, mock_scroll, obstacle_guard, mock_context):
        mock_instance = MagicMock()
        mock_instance.perceive.return_value = SituationType.NORMAL
        mock_sae.get_instance.return_value = mock_instance

        mock_context.device.dump_hierarchy.return_value = "<xml>dummy</xml>"
        mock_context.shared_state["consecutive_marker_misses"] = 1

        result = obstacle_guard.execute(mock_context)

        assert result.executed is True
        assert mock_context.shared_state["consecutive_marker_misses"] == 2
        mock_scroll.assert_called_once_with(mock_context.device)

    @patch("GramAddict.core.behaviors.obstacle_guard.TelepathicEngine")
    @patch("GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine")
    @patch("GramAddict.core.behaviors.obstacle_guard.sleep")
    def test_execute_obstacle_miss_1(self, mock_sleep, mock_sae, mock_telepathic, obstacle_guard, mock_context):
        mock_instance = MagicMock()
        mock_instance.perceive.return_value = SituationType.OBSTACLE_MODAL
        mock_sae.get_instance.return_value = mock_instance

        mock_context.device.dump_hierarchy.return_value = "<xml>dummy</xml>"
        mock_context.shared_state["consecutive_marker_misses"] = 0

        result = obstacle_guard.execute(mock_context)

        assert result.executed is True
        assert mock_context.shared_state["consecutive_marker_misses"] == 1
        mock_context.device.press.assert_called_once_with("back")
        mock_sleep.assert_called_once()

    @patch("GramAddict.core.behaviors.obstacle_guard.TelepathicEngine")
    @patch("GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine")
    @patch("GramAddict.core.behaviors.obstacle_guard.sleep")
    def test_execute_obstacle_miss_2_recovery(
        self, mock_sleep, mock_sae, mock_telepathic, obstacle_guard, mock_context
    ):
        mock_instance = MagicMock()
        mock_instance.perceive.return_value = SituationType.OBSTACLE_MODAL
        mock_sae.get_instance.return_value = mock_instance

        mock_tele = MagicMock()
        mock_tele.find_best_node.return_value = {"x": 10, "y": 20, "semantic": "Dismiss"}
        mock_telepathic.get_instance.return_value = mock_tele

        # Before recovery
        mock_context.device.dump_hierarchy.side_effect = ["<xml>dummy</xml>", "<xml>row_feed_button_like</xml>"]
        mock_context.shared_state["consecutive_marker_misses"] = 1

        result = obstacle_guard.execute(mock_context)

        assert result.executed is True
        assert mock_context.shared_state["consecutive_marker_misses"] == 0

    @patch("GramAddict.core.behaviors.obstacle_guard.dump_ui_state")
    @patch("GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine")
    def test_execute_obstacle_miss_3_abort(self, mock_sae, mock_dump, obstacle_guard, mock_context):
        from GramAddict.core.situational_awareness import SituationalAwarenessEngine

        mock_instance = create_autospec(SituationalAwarenessEngine, instance=True)
        mock_instance.perceive.return_value = SituationType.OBSTACLE_MODAL
        mock_sae.get_instance.return_value = mock_instance

        xml_content = "<xml>dummy</xml>"
        mock_context.device.dump_hierarchy.return_value = xml_content
        mock_context.device.get_info.return_value = {"displayWidth": 1080, "displayHeight": 2400}
        mock_context.shared_state["consecutive_marker_misses"] = 2
        mock_context.session_state.job_target = "Feed"

        result = obstacle_guard.execute(mock_context)

        assert result.executed is True
        assert result.metadata.get("return_code") == "CONTEXT_LOST"
        mock_instance.unlearn_current_state.assert_called_once_with(xml_content)
        mock_dump.assert_called_once()
