import numpy as np

from src.gameplay.core.middlewares.radar import setRadarMiddleware, setWaypointIndexMiddleware


def test_radar_does_not_invent_coordinate_when_detection_fails(mocker):
    mocker.patch('src.gameplay.core.middlewares.radar.getCoordinate', return_value=None)
    last_coordinate = mocker.patch(
        'src.gameplay.core.middlewares.radar.LAST_COORDINATE_PATH',
    )
    last_coordinate.exists.return_value = False
    resume = mocker.patch('src.gameplay.core.middlewares.radar.RESUME_PATH')
    resume.exists.return_value = False
    release = mocker.patch(
        'src.gameplay.core.middlewares.radar.releaseKeys',
        side_effect=lambda context: context,
    )
    context = {
        'ng_screenshot': object(),
        'ng_radar': {'coordinate': None, 'previousCoordinate': None},
        'ng_lastPressedKey': 'up',
    }

    assert setRadarMiddleware(context)['ng_radar'] == {
        'coordinate': None,
        'previousCoordinate': None,
    }
    release.assert_called_once_with(context)


def test_radar_revalidates_last_confirmed_coordinate(mocker):
    get_coordinate = mocker.patch(
        'src.gameplay.core.middlewares.radar.getCoordinate',
        side_effect=[None, (32367, 32221, 7)],
    )
    last_coordinate = mocker.patch(
        'src.gameplay.core.middlewares.radar.LAST_COORDINATE_PATH',
    )
    last_coordinate.exists.return_value = True
    last_coordinate.read_text.return_value = '[32367, 32225, 7]'
    resume = mocker.patch('src.gameplay.core.middlewares.radar.RESUME_PATH')
    resume.exists.return_value = False
    context = {
        'ng_screenshot': object(),
        'ng_radar': {'coordinate': None, 'previousCoordinate': None},
        'ng_lastPressedKey': None,
    }

    assert setRadarMiddleware(context)['ng_radar']['coordinate'] == (32367, 32221, 7)
    assert get_coordinate.call_count == 2


def test_radar_uses_explicit_one_shot_resume(mocker):
    mocker.patch(
        'src.gameplay.core.middlewares.radar.getCoordinate', return_value=None,
    )
    resume = mocker.patch('src.gameplay.core.middlewares.radar.RESUME_PATH')
    resume.exists.return_value = True
    resume.read_text.return_value = (
        '{"waypointIndex": 1, "coordinate": [32394, 32234, 7]}'
    )
    context = {
        'ng_screenshot': object(),
        'ng_radar': {'coordinate': None, 'previousCoordinate': None},
        'ng_cave': {'waypoints': {
            'currentIndex': None,
            'items': [
                {'coordinate': [32390, 32215, 7]},
                {'coordinate': [32386, 32215, 7]},
            ],
        }},
        'gameWindow': {'coordinate': None},
    }

    result = setRadarMiddleware(context)

    assert result['ng_radar']['coordinate'] == (32394, 32234, 7)
    assert result['ng_cave']['waypoints']['currentIndex'] == 1
    resume.unlink.assert_called_once_with()


def test_radar_prefers_live_coordinate(mocker):
    mocker.patch(
        'src.gameplay.core.middlewares.radar.getCoordinate',
        return_value=(32348, 32227, 7),
    )
    context = {
        'ng_screenshot': object(),
        'ng_radar': {'coordinate': None, 'previousCoordinate': None},
    }

    result = setRadarMiddleware(context)

    assert result['ng_radar']['coordinate'] == (32348, 32227, 7)


def test_radar_rejects_coordinate_jitter_without_visible_movement(mocker):
    mocker.patch(
        'src.gameplay.core.middlewares.radar.getCoordinate',
        return_value=(32384, 32212, 7),
    )
    mocker.patch(
        'src.gameplay.core.middlewares.radar.cv2.phaseCorrelate',
        return_value=((0.01, -0.02), 0.95),
    )
    context = {
        'ng_screenshot': np.zeros((20, 20), dtype=np.uint8),
        'ng_radar': {
            'coordinate': (32384, 32215, 7),
            'previousCoordinate': (32384, 32215, 7),
            'previousGameWindowSample': np.zeros((10, 10), dtype=np.uint8),
        },
        'gameWindow': {'coordinate': (0, 0, 10, 10)},
    }

    result = setRadarMiddleware(context)

    assert result['ng_radar']['coordinate'] == (32384, 32215, 7)


def test_radar_accepts_coordinate_after_visible_movement(mocker):
    mocker.patch(
        'src.gameplay.core.middlewares.radar.getCoordinate',
        return_value=(32385, 32215, 7),
    )
    mocker.patch(
        'src.gameplay.core.middlewares.radar.cv2.phaseCorrelate',
        return_value=((-64, 0), 0.95),
    )
    context = {
        'ng_screenshot': np.zeros((20, 20), dtype=np.uint8),
        'ng_radar': {
            'coordinate': (32384, 32215, 7),
            'previousCoordinate': (32384, 32215, 7),
            'previousGameWindowSample': np.zeros((10, 10), dtype=np.uint8),
        },
        'gameWindow': {'coordinate': (0, 0, 10, 10)},
    }

    result = setRadarMiddleware(context)

    assert result['ng_radar']['coordinate'] == (32385, 32215, 7)


def test_radar_accumulates_partial_camera_motion(mocker):
    mocker.patch(
        'src.gameplay.core.middlewares.radar.getCoordinate',
        return_value=(32384, 32215, 7),
    )
    mocker.patch(
        'src.gameplay.core.middlewares.radar.cv2.phaseCorrelate',
        side_effect=[((-32, 0), 0.95), ((-32, 0), 0.95)],
    )
    context = {
        'ng_screenshot': np.zeros((20, 20), dtype=np.uint8),
        'ng_radar': {
            'coordinate': (32384, 32215, 7),
            'previousCoordinate': (32384, 32215, 7),
            'previousGameWindowSample': np.zeros((10, 10), dtype=np.uint8),
        },
        'gameWindow': {'coordinate': (0, 0, 10, 10)},
    }

    first = setRadarMiddleware(context)
    assert first['ng_radar']['coordinate'] == (32384, 32215, 7)
    first['ng_radar']['previousCoordinate'] = first['ng_radar']['coordinate']

    second = setRadarMiddleware(first)

    assert second['ng_radar']['coordinate'] == (32385, 32215, 7)


def test_radar_does_not_advance_when_camera_is_blocked(mocker):
    mocker.patch(
        'src.gameplay.core.middlewares.radar.getCoordinate',
        return_value=(32385, 32215, 7),
    )
    mocker.patch(
        'src.gameplay.core.middlewares.radar.cv2.phaseCorrelate',
        return_value=((0.0, 0.0), 0.95),
    )
    context = {
        'ng_screenshot': np.zeros((20, 20), dtype=np.uint8),
        'ng_radar': {
            'coordinate': (32384, 32215, 7),
            'previousCoordinate': (32384, 32215, 7),
            'previousGameWindowSample': np.zeros((10, 10), dtype=np.uint8),
        },
        'gameWindow': {'coordinate': (0, 0, 10, 10)},
    }

    result = setRadarMiddleware(context)

    assert result['ng_radar']['coordinate'] == (32384, 32215, 7)


def test_waypoint_selection_waits_for_coordinate(mocker):
    path = mocker.patch('src.gameplay.core.middlewares.radar.generateFloorWalkpoints')
    context = {
        'ng_radar': {'coordinate': None},
        'ng_cave': {'waypoints': {'currentIndex': None, 'items': [{}]}},
    }

    assert setWaypointIndexMiddleware(context) is context
    path.assert_not_called()


def test_waypoint_selection_skips_unreachable_nearest_waypoint(mocker):
    path = mocker.patch(
        'src.gameplay.core.middlewares.radar.generateFloorWalkpoints',
        side_effect=[[], [[32366, 32233, 7]]],
    )
    context = {
        'ng_radar': {'coordinate': (32365, 32234, 7)},
        'ng_cave': {'waypoints': {
            'currentIndex': None,
            'items': [
                {'coordinate': [32364, 32234, 7]},
                {'coordinate': [32366, 32232, 7]},
            ],
        }},
    }

    assert setWaypointIndexMiddleware(context) is context
    assert context['ng_cave']['waypoints']['currentIndex'] == 1
    assert path.call_count == 2


def test_waypoint_selection_uses_nearest_when_all_are_unreachable(mocker):
    path = mocker.patch(
        'src.gameplay.core.middlewares.radar.generateFloorWalkpoints',
        return_value=[],
    )
    context = {
        'ng_radar': {'coordinate': (32400, 32230, 8)},
        'ng_cave': {'waypoints': {
            'currentIndex': None,
            'items': [
                {'coordinate': [32349, 32230, 8]},
                {'coordinate': [32430, 32303, 8]},
            ],
        }},
    }

    assert setWaypointIndexMiddleware(context) is context
    assert context['ng_cave']['waypoints']['currentIndex'] == 0
    assert path.call_count == 2
