"""Waechter: im Projekt darf keine Software-Eingabe an Tibia gehen.

Hintergrund: Am 19.07.2026 lief der Bot uebergangsweise mit pyautogui
("software input is sufficient until an Arduino is actually connected").
Der damit benutzte Account wurde geloescht. Software-Eingaben wie
SendInput, keybd_event, mouse_event oder pyautogui erzeugt das
Betriebssystem - sie sind von echten Geraeteeingaben unterscheidbar.
Der 32u4-Chip existiert genau deshalb.

Dieser Test schlaegt fehl, sobald wieder so ein Aufruf ins Projekt
gelangt. Bildschirmfotos (pyautogui.screenshot) bleiben erlaubt, sie
senden nichts.
"""
import ast
from pathlib import Path

import pytest

REPO = Path(__file__).resolve().parents[2]
DURCHSUCHTE_ORDNER = ('src', 'tools')

# Funktionen, die Eingaben ins System schreiben.
VERBOTENE_AUFRUFE = {
    'SendInput', 'keybd_event', 'mouse_event', 'SetCursorPos',
    'press', 'write', 'click', 'hotkey', 'keyDown', 'keyUp',
    'typewrite', 'moveTo', 'dragTo', 'scroll', 'mouseDown', 'mouseUp',
}
VERBOTENE_MODULE = {'pyautogui', 'pydirectinput', 'pynput'}
# Es gibt PyPI-Pakete namens "keyboard" und "mouse", die Eingaben senden.
# Das Projekt hat eigene Module mit diesen Namen (die ueber den Arduino
# gehen) - erlaubt ist daher nur der Import aus src.utils.
EIGENE_MODULNAMEN = {'keyboard', 'mouse'}
# Fenster-Nachrichten, die Tastatur/Maus vortaeuschen.
VERBOTENE_FENSTER_NACHRICHTEN = {
    'WM_KEYDOWN', 'WM_KEYUP', 'WM_CHAR', 'WM_SYSKEYDOWN',
    'WM_LBUTTONDOWN', 'WM_LBUTTONUP', 'WM_RBUTTONDOWN', 'WM_RBUTTONUP',
}


def _python_dateien():
    for ordner in DURCHSUCHTE_ORDNER:
        for pfad in (REPO / ordner).rglob('*.py'):
            if '__pycache__' in pfad.parts:
                continue
            # macOS legt beim Kopieren "._name"-Begleitdateien an; das sind
            # Metadaten, kein Quelltext.
            if pfad.name.startswith('._'):
                continue
            yield pfad


def _verstoesse(pfad):
    baum = ast.parse(pfad.read_text(encoding='utf-8'), filename=str(pfad))
    gefunden = []

    for knoten in ast.walk(baum):
        # import pyautogui / from pynput import ...
        if isinstance(knoten, ast.Import):
            for name in knoten.names:
                wurzel = name.name.split('.')[0]
                if wurzel in VERBOTENE_MODULE or wurzel in EIGENE_MODULNAMEN:
                    gefunden.append(f'Zeile {knoten.lineno}: import {name.name}')
        elif isinstance(knoten, ast.ImportFrom):
            modul = knoten.module or ''
            wurzel = modul.split('.')[0]
            if wurzel in VERBOTENE_MODULE:
                gefunden.append(f'Zeile {knoten.lineno}: from {modul} import ...')
            elif wurzel in EIGENE_MODULNAMEN and not modul.startswith('src.'):
                gefunden.append(
                    f'Zeile {knoten.lineno}: from {modul} import ... '
                    '(fremdes Paket statt src.utils)')
        # pyautogui.press(...) / user32.SendInput(...)
        elif isinstance(knoten, ast.Call):
            ziel = knoten.func
            if isinstance(ziel, ast.Attribute):
                if ziel.attr in {'SendInput', 'keybd_event', 'mouse_event', 'SetCursorPos'}:
                    gefunden.append(f'Zeile {knoten.lineno}: .{ziel.attr}()')
                elif (isinstance(ziel.value, ast.Name)
                      and ziel.value.id in VERBOTENE_MODULE
                      and ziel.attr in VERBOTENE_AUFRUFE):
                    gefunden.append(
                        f'Zeile {knoten.lineno}: {ziel.value.id}.{ziel.attr}()')
        # win32con.WM_KEYDOWN als Argument einer PostMessage/SendMessage
        elif isinstance(knoten, ast.Attribute):
            if knoten.attr in VERBOTENE_FENSTER_NACHRICHTEN:
                gefunden.append(f'Zeile {knoten.lineno}: {knoten.attr}')

    return gefunden


def test_keine_software_eingabe_im_projekt():
    fehler = {}
    for pfad in _python_dateien():
        verstoesse = _verstoesse(pfad)
        if verstoesse:
            fehler[str(pfad.relative_to(REPO))] = verstoesse

    if fehler:
        bericht = '\n'.join(
            f'  {datei}:\n' + '\n'.join(f'    {v}' for v in stellen)
            for datei, stellen in sorted(fehler.items())
        )
        pytest.fail(
            'Software-Eingabe gefunden - das fuehrt zum Verlust des Accounts.\n'
            'Jede Eingabe an Tibia muss ueber den Arduino laufen '
            '(src/utils/keyboard.py, src/utils/mouse.py).\n' + bericht
        )


def test_eingabe_laeuft_ueber_den_arduino():
    """Gegenprobe: die Eingabe-Module sprechen wirklich den Chip an."""
    for name in ('keyboard.py', 'mouse.py'):
        quelltext = (REPO / 'src' / 'utils' / name).read_text(encoding='utf-8')
        assert 'sendCommandArduino' in quelltext, (
            f'src/utils/{name} sendet nicht mehr ueber den Arduino.')
