from unittest.mock import MagicMock, patch

from GramAddict.core.bot_flow import _wait_for_post_loaded


def time_incrementer():
    times = [0, 1, 2, 3, 4, 10, 11, 12, 13, 14, 15]
    for t in times:
        yield t
    while True:
        yield 20


def test_wait_for_post_loaded_success():
    """Test that it returns True if feed markers are found."""
    mock_device = MagicMock()
    mock_device.dump_hierarchy.return_value = '<node resource-id="com.instagram.android:id/row_feed_photo_imageview" />'

    result = _wait_for_post_loaded(mock_device, timeout=1)
    assert result is True


@patch("GramAddict.core.physics.timing.sleep")
@patch("GramAddict.core.physics.timing.dump_ui_state")
def test_wait_for_post_loaded_adaptive_snap_story(mock_dump, mock_sleep):
    """Test that being trapped in a story triggers a back press."""
    mock_device = MagicMock()
    # Simulate a timeout by making time.time() advance
    with patch("time.time", side_effect=time_incrementer()):
        mock_device.dump_hierarchy.return_value = '<node resource-id="com.instagram.android:id/reel_viewer_root" />'

        result = _wait_for_post_loaded(mock_device, timeout=5)

        # It should have timed out, dumped state, and pressed back
        assert mock_dump.called
        mock_device.press.assert_called_with("back")
        # Still returns False if feed markers are not found after recovery
        assert result is False


@patch("GramAddict.core.physics.timing.sleep")
@patch("GramAddict.core.physics.timing.dump_ui_state")
def test_wait_for_post_loaded_adaptive_snap_profile(mock_dump, mock_sleep):
    """Test that being trapped in a profile triggers a back press."""
    mock_device = MagicMock()
    with patch("time.time", side_effect=time_incrementer()):
        mock_device.dump_hierarchy.return_value = '<node resource-id="com.instagram.android:id/profile_header" />'

        result = _wait_for_post_loaded(mock_device, timeout=5)

        mock_device.press.assert_called_with("back")
        assert result is False


@patch("GramAddict.core.physics.timing.sleep")
@patch("GramAddict.core.physics.timing.dump_ui_state")
def test_wait_for_post_loaded_adaptive_snap_wobble(mock_dump, mock_sleep):
    """Test that being stuck between posts triggers a wobble if no nav_graph is provided."""
    mock_device = MagicMock()
    mock_device.get_info.return_value = {"displayWidth": 1080, "displayHeight": 2400}
    with patch("time.time", side_effect=time_incrementer()):
        # No recognized markers
        mock_device.dump_hierarchy.return_value = '<node resource-id="com.instagram.android:id/action_bar_root" />'

        result = _wait_for_post_loaded(mock_device, timeout=5)

        # Should swipe (wobble) twice
        assert mock_device.swipe.call_count == 2
        # Check that duration is explicitly specified and is less than 1.0 to prevent 100-second stalls
        for call in mock_device.swipe.call_args_list:
            args, kwargs = call
            duration = args[4] if len(args) > 4 else kwargs.get("duration", 0.5)
            assert duration <= 1.0, f"Swipe duration is too long: {duration} seconds!"
        assert result is False


@patch("GramAddict.core.physics.timing.sleep")
@patch("GramAddict.core.physics.timing.dump_ui_state")
def test_wait_for_post_loaded_adaptive_snap_align(mock_dump, mock_sleep):
    """Test that being stuck between posts triggers nav_graph.do('align') if nav_graph is provided."""
    mock_device = MagicMock()
    mock_nav_graph = MagicMock()
    with patch("time.time", side_effect=time_incrementer()):
        mock_device.dump_hierarchy.return_value = '<node resource-id="com.instagram.android:id/action_bar_root" />'

        result = _wait_for_post_loaded(mock_device, timeout=5, nav_graph=mock_nav_graph)

        # Now it should unconditionally micro-wobble (swipe twice)
        assert mock_device.swipe.call_count == 2
        for call in mock_device.swipe.call_args_list:
            args, kwargs = call
            duration = args[4] if len(args) > 4 else kwargs.get("duration", 0.5)
            assert duration <= 1.0, f"Swipe duration is too long: {duration} seconds!"
        assert result is False
