import json
import subprocess
import sys
import time
import traceback
from pathlib import Path

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

from src.utils.dpi import enablePerMonitorAwareness

enablePerMonitorAwareness()

import win32api
import win32con
import win32gui
import win32process

from src.utils.display import getSelectedDisplay
from src.utils.minimap import stelleVorlageHer

exe = Path.home() / 'AppData/Local/Tibia/Tibia.exe'
result_path = repo / '.tibia-window.json'
error_path = repo / '.launch-tibia-error.txt'
error_path.unlink(missing_ok=True)


def report_error(error_type, error, trace):
    text = ''.join(traceback.format_exception(error_type, error, trace))
    error_path.write_text(text, encoding='utf-8')
    # Zusaetzlich auf die Fehlerausgabe: seit dem Start ueber die
    # Desktop-Bridge landet die direkt beim Aufrufer, statt nur in
    # einer Datei zu verstauben.
    sys.stderr.write(text)


sys.excepthook = report_error


def bot_monitor():
    display = getSelectedDisplay()
    return (
        display['x'], display['y'],
        display['x'] + display['width'],
        display['y'] + display['height'],
    )


def _is_tibia_process(hwnd):
    # Browser-Tabs o.ae. koennen ebenfalls "Tibia" im Titel tragen -
    # nur Fenster des echten Clients zaehlen.
    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):
        # Prozess schon beendet oder kein Zugriff. Programmierfehler
        # (fehlender Import o. ae.) bewusst NICHT abfangen - genau das
        # hat hier einmal jedes Fenster stillschweigend verworfen.
        return False
    return image.endswith(('\\client.exe', '\\tibia.exe'))


def tibia_windows():
    windows = []

    def collect(hwnd, _):
        if not win32gui.IsWindowVisible(hwnd):
            return
        try:
            title = win32gui.GetWindowText(hwnd)
            if title == 'Tibia' or title.startswith('Tibia - '):
                if _is_tibia_process(hwnd):
                    windows.append((hwnd, title, win32gui.GetWindowRect(hwnd)))
        except win32gui.error:
            # The launcher replaces its HWND during startup; enumerate again.
            return

    win32gui.EnumWindows(collect, None)
    return windows


def restore_foreground(hwnd):
    if not hwnd or not win32gui.IsWindow(hwnd):
        return
    try:
        win32gui.SetForegroundWindow(hwnd)
    except Exception:
        pass


def place_on_bot_monitor(hwnd, bounds):
    if not hwnd or not win32gui.IsWindow(hwnd):
        return False
    left, top, right, bottom = bounds
    try:
        win32gui.ShowWindow(hwnd, win32con.SW_SHOWNOACTIVATE)
        # HWND_BOTTOM places the launcher behind the virtual desktop itself.
        # Keep it visible on the isolated bot screen without stealing focus.
        win32gui.SetWindowPos(
            hwnd, win32con.HWND_TOP,
            left, top, right - left, bottom - top,
            win32con.SWP_SHOWWINDOW | win32con.SWP_NOACTIVATE,
        )
        return True
    except win32gui.error:
        return False


if not exe.exists():
    raise FileNotFoundError(exe)

bounds = bot_monitor()
previous_foreground = win32gui.GetForegroundWindow()
if not tibia_windows():
    # Solange der Client noch nicht laeuft, ist der richtige Moment, die
    # vollstaendige Karte wiederherzustellen - er liest die Kacheln beim
    # Start ein und ueberschreibt sie spaeter mit seinem eigenen Stand.
    # Ein Fehler hier darf den Start nicht verhindern.
    try:
        ersetzt = stelleVorlageHer()
        if ersetzt:
            sys.stderr.write(
                f'Minimap: {ersetzt} Kacheln aus der Vorlage ersetzt\n')
    except OSError as fehler:
        sys.stderr.write(f'Minimap-Vorlage nicht anwendbar: {fehler}\n')
    startup = subprocess.STARTUPINFO()
    startup.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    startup.wShowWindow = win32con.SW_SHOWMINNOACTIVE
    subprocess.Popen([str(exe)], startupinfo=startup)

placed = {}
# Der Launcher-Prozess startet sofort, sein Fenster erscheint aber erst
# nach der Update-Pruefung - gemessen deutlich mehr als 30 Sekunden.
deadline = time.time() + 90
while time.time() < deadline:
    for hwnd, title, _ in tibia_windows():
        if place_on_bot_monitor(hwnd, bounds):
            placed[str(hwnd)] = {
                'title': title,
                'rect': bounds,
            }
            restore_foreground(previous_foreground)
    if placed:
        time.sleep(1)
        break
    else:
        time.sleep(0.05)

restore_foreground(previous_foreground)

result_path.write_text(
    json.dumps({'bounds': bounds, 'windows': list(placed.values())}),
    encoding='utf-8',
)
if not placed:
    # Aussagekraeftig statt ratlos: was lief, und was war zu sehen?
    running = []

    def _describe(hwnd, _):
        title = win32gui.GetWindowText(hwnd)
        if 'tibia' in title.lower():
            running.append(
                f'{title!r} sichtbar={win32gui.IsWindowVisible(hwnd)} '
                f'eigenerClient={_is_tibia_process(hwnd)}')

    win32gui.EnumWindows(_describe, None)
    raise RuntimeError(
        'Das Tibia-Launcher-Fenster ist nicht erschienen (90 Sekunden '
        'gewartet). Gesehene Fenster mit "Tibia" im Titel: '
        + ('; '.join(running) if running else 'keine')
        + f'. Bot-Bildschirm: {bounds}.')
