"""Meldet den Charakter im Tibia-Client an - ausschliesslich ueber den
Arduino (Hardware-Tastatur und -Maus).

WICHTIG: Hier darf NIE Software-Eingabe stehen (SendInput, PostMessage,
keybd_event, pyautogui). Der ganze Sinn des 32u4-Chips ist, dass die
Eingaben als echtes USB-Geraet ankommen und nicht von einer Software
erzeugt aussehen. Eine frueher hier eingebaute SendInput-Variante wurde
genau deshalb wieder entfernt.

Die Feldkoordinaten gelten fuer ein randloses Fenster mit exakt
1920x1080 Spielflaeche (siehe tools\\Set-Client-Window.py). Sie wurden am
Login-Dialog ausgemessen; E-Mail- und Passwortfeld liegen nur 31 Pixel
auseinander, darum wird die Fenstergroesse vorher geprueft.
"""
import json
import sys
import time
import traceback
from pathlib import Path

import win32api
import win32con
import win32gui
import win32process

repo = Path.home() / 'TibiaPilotNG'
sys.path.insert(0, str(repo))

from src.security.credentials import load_credentials
from src.utils.keyboard import press, write
from src.utils.mouse import leftClick

result_path = repo / '.login-result.json'
play_result_path = repo / '.play-tibia.json'
windows = []

# Am Login-Dialog ausgemessen, RELATIV zur linken oberen Fensterecke
# (Spielflaeche 1920x1080). Absolute Bildschirmkoordinaten waeren falsch,
# sobald der Bot-Bildschirm nicht bei 0,0 liegt - der virtuelle Schirm
# beginnt zum Beispiel bei x=6400.
EMAIL_FIELD = (1060, 489)
PASSWORD_FIELD = (1060, 520)
LOGIN_BUTTON = (1041, 606)
EXPECTED_SIZE = (1920, 1080)
# Zeit fuer die Antwort des Anmeldeservers, bevor die Charakterauswahl
# bestaetigt wird.
CHARACTER_LIST_DELAY = 6
# Reicht fuer eine lange E-Mail-Adresse; der Klick setzt den Cursor
# rechts neben den Text, Backspace raeumt nach links auf.
CLEAR_STROKES = 45


def collect(hwnd, _):
    title = win32gui.GetWindowText(hwnd)
    if not win32gui.IsWindowVisible(hwnd):
        return
    if title != 'Tibia' and not title.startswith('Tibia - '):
        return
    # Browser-Tabs o.ae. koennen ebenfalls "Tibia" im Titel tragen.
    try:
        pid = win32process.GetWindowThreadProcessId(hwnd)[1]
        process = win32api.OpenProcess(
            win32con.PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
        try:
            image = win32process.GetModuleFileNameEx(process, 0).lower()
        finally:
            win32api.CloseHandle(process)
    except (OSError, win32api.error):
        # Nur "Prozess weg / kein Zugriff" abfangen. Ein zu breites
        # except verschluckt Programmierfehler und verwirft dann
        # stillschweigend jedes Fenster.
        return
    if image.endswith(('\\client.exe', '\\tibia.exe')):
        windows.append((hwnd, title))


def clear_field(position):
    """Feld leeren: hineinklicken (Cursor landet hinter dem Text) und
    zurueckloeschen. Strg+A ueber den Arduino war unzuverlaessig."""
    leftClick(position)
    press('end')
    for _ in range(CLEAR_STROKES):
        press('backspace')


try:
    credentials = load_credentials()
    if credentials is None:
        raise ValueError('Tibia credentials are not configured.')
    password = credentials['password']
    win32gui.EnumWindows(collect, None)
    if not windows:
        raise RuntimeError('Tibia window not found.')
    expected_hwnd = None
    if play_result_path.exists():
        expected_hwnd = json.loads(
            play_result_path.read_text(encoding='utf-8-sig'),
        ).get('gameHwnd')
    hwnd, _ = next(
        (window for window in windows if window[0] == expected_hwnd),
        windows[0],
    )

    # Die Koordinaten unten gelten nur fuer die erwartete Fenstergroesse.
    # Lieber klar abbrechen als blind danebenklicken.
    left, top, right, bottom = win32gui.GetWindowRect(hwnd)
    size = (right - left, bottom - top)
    if size != EXPECTED_SIZE:
        raise RuntimeError(
            f'Das Tibia-Fenster ist {size[0]}x{size[1]} statt '
            f'{EXPECTED_SIZE[0]}x{EXPECTED_SIZE[1]}. Der Bot braucht ein '
            'randloses Fenster in dieser Groesse '
            '(tools\\Set-Client-Window.py).')

    # Ganz nach oben holen: der Arduino klickt auf Bildschirmkoordinaten,
    # ein darueberliegendes Fenster wuerde den Klick abfangen.
    win32gui.SetWindowPos(
        hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0,
        win32con.SWP_NOMOVE | win32con.SWP_NOSIZE,
    )
    time.sleep(0.3)

    # Fensterecke draufrechnen: der Arduino klickt auf absolute
    # Bildschirmpunkte, das Fenster kann aber irgendwo liegen.
    def am_fenster(punkt):
        return (left + punkt[0], top + punkt[1])

    clear_field(am_fenster(EMAIL_FIELD))
    write(credentials['account'])
    clear_field(am_fenster(PASSWORD_FIELD))
    write(password)

    # GENAU EIN Anmeldeversuch pro Lauf.
    #
    # Frueher stand hier eine Schleife, die alle 8 Sekunden erneut Enter
    # drueckte, bis der Charakter im Spiel war. Jeder dieser Tastendruecke
    # war fuer den Server ein weiterer Anmeldeversuch - nach ein paar
    # Fehlschlaegen sperrt Tibia die IP-Adresse fuer 30 Minuten. Genau so
    # ist das hier einmal passiert. Darum: einmal anmelden, dann nur noch
    # zuschauen.
    leftClick(am_fenster(LOGIN_BUTTON))

    # Nach erfolgreicher Anmeldung erscheint die Charakterauswahl. Ein
    # einzelnes Enter startet den vorgewaehlten Charakter.
    time.sleep(CHARACTER_LIST_DELAY)
    title = win32gui.GetWindowText(hwnd)
    if not title.startswith('Tibia - '):
        press('enter')

    deadline = time.time() + 60
    while time.time() < deadline:
        title = win32gui.GetWindowText(hwnd)
        if title.startswith('Tibia - '):
            break
        time.sleep(0.25)
    if not title.startswith('Tibia - '):
        raise RuntimeError(
            'Der Charakter ist nach dem Anmeldeversuch nicht ins Spiel '
            'gekommen (60 Sekunden gewartet). Moegliche Gruende: falsche '
            'Zugangsdaten, der Server ist nicht erreichbar, oder die '
            'IP-Adresse ist nach zu vielen Fehlversuchen fuer 30 Minuten '
            'gesperrt. Ein Blick auf den Bot-Bildschirm zeigt die genaue '
            'Meldung des Clients.')
    win32gui.SetWindowPos(
        hwnd, win32con.HWND_NOTOPMOST, 0, 0, 0, 0,
        win32con.SWP_NOMOVE | win32con.SWP_NOSIZE,
    )
    result = {'ok': True, 'window': title}
except Exception:
    result = {'ok': False, 'error': traceback.format_exc()}
    sys.stderr.write(result['error'])
    raise
finally:
    result_path.write_text(json.dumps(result), encoding='utf-8')
