import numpy as np
import cv2

from src.repositories.radar import config
from src.repositories.radar import core


def test_client_minimap_tiles_are_laid_over_static_floor(tmp_path):
    """Kacheln des Clients ergaenzen die mitgelieferte Karte.

    Frueher wurde die komplette Etage durch eine leere Leinwand ersetzt -
    von einer vollstaendig bekannten Etage blieb dann nur der selbst
    erkundete Ausschnitt uebrig, und der Bot fand seine Position nicht.
    """
    fallback = [np.full((258, 258), 9, dtype=np.uint8) for _ in range(16)]
    tile = np.full((256, 256), 7, dtype=np.uint8)
    cv2.imwrite(
        str(tmp_path / 'Minimap_Color_31745_30977_7.png'),
        tile,
    )

    result = config._loadClientFloorImages(fallback, tmp_path)

    assert np.array_equal(result[6], fallback[6])
    # Kachel liegt drauf ...
    assert np.all(result[7][1:257, 1:257] == 7)
    # ... und ausserhalb bleibt die mitgelieferte Karte erhalten.
    assert np.all(result[7][0] == 9)
    assert np.all(result[7][:, 0] == 9)


def test_unexplored_client_pixels_keep_static_floor(tmp_path):
    """Noch nicht erkundete Stellen (Wert 0) duerfen keine Loecher stanzen."""
    fallback = [np.full((258, 258), 9, dtype=np.uint8) for _ in range(16)]
    tile = np.full((256, 256), 7, dtype=np.uint8)
    tile[:128] = 0  # obere Haelfte im Client noch unbekannt
    cv2.imwrite(
        str(tmp_path / 'Minimap_Color_31745_30977_7.png'),
        tile,
    )

    result = config._loadClientFloorImages(fallback, tmp_path)

    # Unbekannter Teil der Kachel: mitgelieferte Karte bleibt stehen.
    assert np.all(result[7][1:129, 1:257] == 9)
    # Erkundeter Teil: Kachel gewinnt.
    assert np.all(result[7][129:257, 1:257] == 7)


def test_mask_below_its_threshold_does_not_replace_valid_raw_match(mocker):
    floor = np.zeros((300, 300), dtype=np.uint8)
    radar = np.zeros((10, 10), dtype=np.uint8)
    route = [[31894, 31126, 7]]
    mocker.patch.object(core, 'RADAR_SCALES', (1.0,))
    mocker.patch.object(
        core.cv2,
        'matchTemplate',
        side_effect=[
            np.array([[0.75]], dtype=np.float32),
            np.array([[0.70]], dtype=np.float32),
            np.array([[0.80]], dtype=np.float32),
        ],
    )

    match = core._locateRadar(floor, radar, routeCoordinates=route)

    assert match is not None
    assert match[0] == 0.75
    assert match[3] == 0.5


def test_coordinate_falls_back_from_stale_anchor_to_route(mocker):
    screenshot = np.zeros((1080, 1920), dtype=np.uint8)
    radar = np.zeros((109, 106), dtype=np.uint8)
    previous = [32428, 32292, 7]
    waypoints = [
        {'coordinate': [32365, 32234, 7]},
        {'coordinate': [32428, 32314, 8]},
    ]
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(1, 2, 3, 4))
    mocker.patch.object(core, 'getRadarImage', return_value=radar)
    mocker.patch.object(core, 'hashit', return_value=123)
    mocker.patch.object(core, 'getFloorLevel', return_value=7)
    locate = mocker.patch.object(
        core, '_locateRadar', side_effect=[None, (0.95, 621, 1258, 0.9)],
    )

    result = core.getCoordinate(
        screenshot, previousCoordinate=previous, waypoints=waypoints,
    )

    assert result == (32365, 32234, 7)
    assert locate.call_count == 2
    assert locate.call_args_list[0].kwargs['previousCoordinate'] == previous
    assert locate.call_args_list[1].kwargs == {
        'routeCoordinates': [[32365, 32234, 7]],
    }


def test_coordinate_accepts_confident_route_match(mocker):
    screenshot = np.zeros((1080, 1920), dtype=np.uint8)
    radar = np.zeros((109, 106), dtype=np.uint8)
    waypoints = [{'coordinate': [32365, 32234, 7]}]
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(1, 2, 3, 4))
    mocker.patch.object(core, 'getRadarImage', return_value=radar)
    mocker.patch.object(core, 'hashit', return_value=123)
    mocker.patch.object(core, 'getFloorLevel', return_value=7)
    mocker.patch.object(core, '_locateRadar', return_value=(0.95, 826, 1173, 0.9))

    assert core.getCoordinate(screenshot, waypoints=waypoints) == (32570, 32149, 7)


def test_floor_detection_accepts_current_client_render(mocker):
    screenshot = np.zeros((1080, 1920), dtype=np.uint8)
    scores = [0.30] * 16
    scores[0] = 0.3963
    scores[7] = 0.4780
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(1870, 61, 20, 60))
    mocker.patch.object(core, 'hashit', return_value=123)
    mocker.patch.object(core, 'floorsLevelsImgsHashes', {})
    mocker.patch.object(
        core.cv2, 'matchTemplate',
        side_effect=[np.array([[score]], dtype=np.float32) for score in scores],
    )

    assert core.getFloorLevel(screenshot) == 7
