import numpy as np

from src.gameplay import lootRouter as routerModule
from src.gameplay.lootRouter import routeLoot


def _context(rules):
    return {
        'ng_screenshot': np.zeros((200, 400), dtype=np.uint8),
        'ng_lootRules': rules,
    }


def test_routes_item_to_target_backpack(mocker):
    containers = {
        'Fur Backpack': (1500, 100, 150, 14),
        'Gold Backpack': (1500, 300, 150, 14),
    }
    mocker.patch.object(
        routerModule, 'detectOpenBackpacks', return_value=containers)
    mocker.patch.object(
        routerModule, 'getItemIcon', return_value=np.zeros((32, 32)))
    mocker.patch.object(
        routerModule, '_findInContainer',
        side_effect=lambda *args, **kwargs: (1510, 120))
    dragSpy = mocker.patch.object(routerModule, 'drag')
    context = _context([{'itemId': 3031, 'to': 'Gold Backpack'}])

    routeLoot(context)

    dragSpy.assert_called_once_with((1510, 120), (1516, 334))


def test_no_rules_no_scan(mocker):
    scanSpy = mocker.patch.object(routerModule, 'detectOpenBackpacks')

    routeLoot(_context([]))

    scanSpy.assert_not_called()


def test_missing_target_backpack_skips(mocker):
    mocker.patch.object(
        routerModule, 'detectOpenBackpacks',
        return_value={'Fur Backpack': (1500, 100, 150, 14)})
    mocker.patch.object(
        routerModule, 'getItemIcon', return_value=np.zeros((32, 32)))
    dragSpy = mocker.patch.object(routerModule, 'drag')
    context = _context([{'itemId': 3031, 'to': 'Gold Backpack'}])

    routeLoot(context)

    dragSpy.assert_not_called()


def test_item_not_found_no_drag(mocker):
    containers = {
        'Fur Backpack': (1500, 100, 150, 14),
        'Gold Backpack': (1500, 300, 150, 14),
    }
    mocker.patch.object(
        routerModule, 'detectOpenBackpacks', return_value=containers)
    mocker.patch.object(
        routerModule, 'getItemIcon', return_value=np.zeros((32, 32)))
    mocker.patch.object(
        routerModule, '_findInContainer', return_value=None)
    dragSpy = mocker.patch.object(routerModule, 'drag')
    context = _context([{'itemId': 3031, 'to': 'Gold Backpack'}])

    routeLoot(context)

    dragSpy.assert_not_called()


def test_drop_rule_throws_trash_on_player_sqm(mocker):
    containers = {'Fur Backpack': (1500, 100, 150, 14)}
    mocker.patch.object(
        routerModule, 'detectOpenBackpacks', return_value=containers)
    mocker.patch.object(
        routerModule, 'getItemIcon', return_value=np.zeros((32, 32)))
    mocker.patch.object(
        routerModule, '_findInContainer',
        side_effect=lambda *args, **kwargs: (1510, 120))
    mocker.patch.object(
        routerModule, 'getSlotPosition', return_value=(800, 400))
    dragSpy = mocker.patch.object(routerModule, 'drag')
    context = _context([{'itemId': 3577, 'drop': True}])
    context['gameWindow'] = {'coordinate': (100, 100, 960, 540)}

    routeLoot(context)

    dragSpy.assert_called_once_with((1510, 120), (800, 400))


def test_drop_runs_before_move(mocker):
    containers = {
        'Fur Backpack': (1500, 100, 150, 14),
        'Demon Backpack': (1500, 300, 150, 14),
    }
    mocker.patch.object(
        routerModule, 'detectOpenBackpacks', return_value=containers)
    mocker.patch.object(
        routerModule, 'getItemIcon', return_value=np.zeros((32, 32)))
    mocker.patch.object(
        routerModule, '_findInContainer',
        side_effect=lambda *args, **kwargs: (1510, 120))
    mocker.patch.object(
        routerModule, 'getSlotPosition', return_value=(800, 400))
    dragSpy = mocker.patch.object(routerModule, 'drag')
    context = _context([
        {'itemId': 3031, 'to': 'Demon Backpack'},
        {'itemId': 3577, 'drop': True},
    ])
    context['gameWindow'] = {'coordinate': (100, 100, 960, 540)}

    routeLoot(context)

    assert dragSpy.call_args_list[0][0][0] == (1510, 120)
    assert dragSpy.call_args_list[0][0][1] == (800, 400)  # erst Drop
    assert dragSpy.call_args_list[1][0][1] == (1516, 334)  # dann Move


def test_drop_unwanted_drops_recognized_unruled_items(mocker, tmp_path):
    for itemId in (3577, 266, 3031):
        (tmp_path / f'{itemId}.png').write_bytes(b'x')
    mocker.patch(
        'src.repositories.actionBar.slotIcons._itemsPath', tmp_path)
    containers = {'Fur Backpack': (1500, 100, 150, 14)}
    mocker.patch.object(
        routerModule, 'detectOpenBackpacks', return_value=containers)
    mocker.patch.object(
        routerModule, 'getItemIcon', return_value=np.zeros((32, 32)))
    scores = {3577: 0.92}
    mocker.patch.object(
        routerModule, 'matchScore',
        side_effect=lambda region, icon: scores.get(id(icon), 0.92))
    mocker.patch.object(
        routerModule, '_findInContainer',
        side_effect=lambda *args, **kwargs: (1510, 120))
    mocker.patch.object(
        routerModule, 'getSlotPosition', return_value=(800, 400))
    dragSpy = mocker.patch.object(routerModule, 'drag')
    context = _context([])
    context['ng_lootDropUnwanted'] = True
    context['gameWindow'] = {'coordinate': (100, 100, 960, 540)}

    routeLoot(context)

    # 3577 (Muell) wird geworfen; 266 (Trank) und 3031 (Coin) bleiben
    assert dragSpy.call_count == 1
    dragSpy.assert_called_once_with((1510, 120), (800, 400))


def test_drop_unwanted_never_touches_rule_targets(mocker, tmp_path):
    (tmp_path / '3577.png').write_bytes(b'x')
    mocker.patch(
        'src.repositories.actionBar.slotIcons._itemsPath', tmp_path)
    containers = {
        'Fur Backpack': (1500, 100, 150, 14),
        'Demon Backpack': (1500, 300, 150, 14),
    }
    mocker.patch.object(
        routerModule, 'detectOpenBackpacks', return_value=containers)
    mocker.patch.object(
        routerModule, 'getItemIcon', return_value=np.zeros((32, 32)))
    mocker.patch.object(
        routerModule, 'matchScore', return_value=0.92)
    mocker.patch.object(
        routerModule, '_findInContainer', return_value=None)
    mocker.patch.object(
        routerModule, 'getSlotPosition', return_value=(800, 400))
    dragSpy = mocker.patch.object(routerModule, 'drag')
    context = _context([{'itemId': 3031, 'to': 'Demon Backpack'}])
    context['ng_lootDropUnwanted'] = True
    context['gameWindow'] = {'coordinate': (100, 100, 960, 540)}

    routeLoot(context)

    # Demon Backpack ist Regel-Ziel und wird nicht gescannt
    dragSpy.assert_not_called()


def test_drop_unwanted_off_by_default(mocker, tmp_path):
    (tmp_path / '3577.png').write_bytes(b'x')
    mocker.patch(
        'src.repositories.actionBar.slotIcons._itemsPath', tmp_path)
    mocker.patch.object(
        routerModule, 'detectOpenBackpacks',
        return_value={'Fur Backpack': (1500, 100, 150, 14)})
    dragSpy = mocker.patch.object(routerModule, 'drag')
    context = _context([])
    context['gameWindow'] = {'coordinate': (100, 100, 960, 540)}

    routeLoot(context)

    dragSpy.assert_not_called()


def test_ambiguous_icons_are_never_dropped(mocker, tmp_path):
    for itemId in (3577, 5001):
        (tmp_path / f'{itemId}.png').write_bytes(b'x')
    mocker.patch(
        'src.repositories.actionBar.slotIcons._itemsPath', tmp_path)
    containers = {'Fur Backpack': (1500, 100, 150, 14)}
    mocker.patch.object(
        routerModule, 'detectOpenBackpacks', return_value=containers)
    iconA, iconB = np.zeros((32, 32)), np.ones((32, 32))
    icons = {3577: iconA, 5001: iconB}
    mocker.patch.object(
        routerModule, 'getItemIcon', side_effect=lambda itemId: icons.get(itemId))
    scores = {id(iconA): 0.90, id(iconB): 0.87}
    mocker.patch.object(
        routerModule, 'matchScore',
        side_effect=lambda region, icon: scores[id(icon)])
    findSpy = mocker.patch.object(routerModule, '_findInContainer')
    dragSpy = mocker.patch.object(routerModule, 'drag')
    context = _context([])
    context['ng_lootDropUnwanted'] = True
    context['gameWindow'] = {'coordinate': (100, 100, 960, 540)}

    routeLoot(context)

    findSpy.assert_not_called()
    dragSpy.assert_not_called()


def test_clear_winner_is_dropped(mocker, tmp_path):
    for itemId in (3577, 5001):
        (tmp_path / f'{itemId}.png').write_bytes(b'x')
    mocker.patch(
        'src.repositories.actionBar.slotIcons._itemsPath', tmp_path)
    containers = {'Fur Backpack': (1500, 100, 150, 14)}
    mocker.patch.object(
        routerModule, 'detectOpenBackpacks', return_value=containers)
    iconA, iconB = np.zeros((32, 32)), np.ones((32, 32))
    icons = {3577: iconA, 5001: iconB}
    mocker.patch.object(
        routerModule, 'getItemIcon', side_effect=lambda itemId: icons.get(itemId))
    scores = {id(iconA): 0.93, id(iconB): 0.60}
    mocker.patch.object(
        routerModule, 'matchScore',
        side_effect=lambda region, icon: scores[id(icon)])
    mocker.patch.object(
        routerModule, '_findInContainer', return_value=(1510, 120))
    mocker.patch.object(
        routerModule, 'getSlotPosition', return_value=(800, 400))
    dragSpy = mocker.patch.object(routerModule, 'drag')
    context = _context([])
    context['ng_lootDropUnwanted'] = True
    context['gameWindow'] = {'coordinate': (100, 100, 960, 540)}

    routeLoot(context)

    dragSpy.assert_called_once_with((1510, 120), (800, 400))
