"""Smart-Recovery: Der Bot bleibt niemals haengen.

Beobachtet die Position im Loop und eskaliert bei Stillstand gestuft,
statt denselben Wegpunkt endlos neu zu versuchen:
1. Seitenschritt (rotierende Richtung) — loest Moebel-/Wand-Ecken
2. Smart-Detour — flankiert seitlich auf geprueft begehbare SQMs
3. Wegpunkt ueberspringen — der Wegpunkt selbst ist unerreichbar
4. KI-Blick (optional) — ein lokales Vision-LLM analysiert den
   Screenshot und nennt eine freie Richtung (Umgebungsvariable
   TIBIAPILOT_LLM_URL; ohne Server wird die Stufe uebersprungen)
5. Route neu starten (Label "Start", sonst Index 0) — kompletter Reset
6. Aufgeben: pausieren + Event-Log — Mensch soll nachsehen

Stehen ist legitim bei Kampf, NPC-/Bank-Aktionen und Loot; nur echte
Bewegungsphasen werden bewertet. Jede Koordinaten-Aenderung setzt die
Leiter zurueck.
"""
import base64
import json
import os
import random
import urllib.request
from time import monotonic

import cv2

from src.gameplay.llmWorker import PENDING, poll, submit
from src.gameplay.monitoring import logEvent
from src.repositories.gameWindow.core import getSlotFromCoordinate
from src.repositories.gameWindow.slot import clickSlot
from src.repositories.radar.core import isCoordinateWalkable
from src.utils.array import getNextArrayIndex
from src.utils.keyboard import press

# Sekunden Stillstand → Eskalationsstufe 1..6
STUCK_LEVEL_SECONDS = (20, 40, 60, 90, 120, 150)
_SIDESTEPS = ('left', 'down', 'right', 'up')

_MOVING_TASKS = {
    'walkToWaypoint', 'singleWalk', 'singleMove', 'clickInCoordinate',
    'rightClickUseWaypoint', 'useRopeWaypoint', 'useShovelWaypoint',
    'useLadderWaypoint', 'walkToTargetCreature', 'walkToWaypointDirection',
}

_LLM_PROMPT = (
    'You analyze a screenshot of the 2D top-down MMORPG Tibia. The player '
    'character stands at the center and is stuck: an automated bot cannot '
    'reach its next waypoint. Look at the surroundings (walls, furniture, '
    'fire fields, water, holes, monsters, closed doors). Decide which '
    'cardinal direction the player should walk a few steps to get around '
    'the obstacle. Answer ONLY with JSON: '
    '{"direction": "north|east|south|west", "reason": "short english phrase"}'
)

_LLM_DIRECTION_KEYS = {
    'north': 'up', 'east': 'right', 'south': 'down', 'west': 'left',
}

# Prozess-Cache fuer die automatische Endpunkt-Erkennung
_LLM_STATE = {'probed': False, 'baseUrl': None, 'model': None}


def _llmEndpoint():
    """LLM-Endpunkt: Env-Override oder lokale Ollama-Auto-Erkennung.

    Ohne TIBIAPILOT_LLM_URL wird einmalig localhost:11434 geprueft
    (inkl. Modell-Check); das Ergebnis wird pro Prozess gecacht.
    """
    if _LLM_STATE['probed']:
        return _LLM_STATE['baseUrl'], _LLM_STATE['model']
    _LLM_STATE['probed'] = True
    model = os.getenv('TIBIAPILOT_LLM_MODEL', 'qwen2.5vl:7b')
    baseUrl = os.getenv('TIBIAPILOT_LLM_URL', '').rstrip('/')
    if baseUrl:
        _LLM_STATE.update(baseUrl=baseUrl, model=model)
        return baseUrl, model
    try:
        with urllib.request.urlopen(
                'http://localhost:11434/api/tags', timeout=2) as response:
            tags = json.loads(response.read().decode('utf-8'))
        family = model.split(':')[0] + ':'
        names = {entry.get('name', '') for entry in tags.get('models', [])}
        if any(name.startswith(family) for name in names):
            _LLM_STATE.update(
                baseUrl='http://localhost:11434', model=model)
    except Exception:
        pass
    return _LLM_STATE['baseUrl'], _LLM_STATE['model']


def _startIndex(items):
    target = next(
        (index for index, waypoint in enumerate(items)
         if (waypoint.get('label') or '').lower() == 'start'),
        None,
    )
    return target if target is not None else 0


def _detour(context):
    """Flankiert seitlich auf begehbare SQMs (Sicht + Radar-Karte)."""
    coordinate = context['ng_radar']['coordinate']
    waypoints = context['ng_cave']['waypoints']
    index = waypoints['currentIndex']
    if index is None or index >= len(waypoints['items']):
        return False
    target = waypoints['items'][index].get('coordinate')
    if target is None or any(value is None for value in target):
        return False
    dx = target[0] - coordinate[0]
    dy = target[1] - coordinate[1]
    if abs(dx) >= abs(dy):
        candidates = [
            (coordinate[0], coordinate[1] + offset, coordinate[2])
            for offset in (-3, 3, -2, 2)
        ]
    else:
        candidates = [
            (coordinate[0] + offset, coordinate[1], coordinate[2])
            for offset in (-3, 3, -2, 2)
        ]
    for candidate in candidates:
        if not isCoordinateWalkable(candidate):
            continue
        slot = getSlotFromCoordinate(coordinate, candidate)
        if slot is None:
            continue
        context['ng_tasksOrchestrator'].reset()
        waypoints['state'] = None
        clickSlot(slot, context['gameWindow']['coordinate'])
        logEvent('recoveryDetour', {'to': list(candidate), 'waypoint': index})
        return True
    return False


def _llmHintPayload(screenshot, model):
    small = cv2.resize(screenshot, None, fx=0.5, fy=0.5)
    ok, buffer = cv2.imencode('.png', small)
    if not ok:
        return None
    return {
        'model': model,
        'stream': False,
        'format': 'json',
        'messages': [{
            'role': 'user', 'content': _LLM_PROMPT,
            'images': [base64.b64encode(buffer).decode('ascii')],
        }],
    }


def _askLlmHint(baseUrl, payload):
    request = urllib.request.Request(
        f'{baseUrl}/api/chat', data=json.dumps(payload).encode('utf-8'),
        headers={'Content-Type': 'application/json'})
    with urllib.request.urlopen(request, timeout=30) as response:
        data = json.loads(response.read().decode('utf-8'))
    return json.loads(data['message']['content'])


def _submitLlmHint(context, recovery):
    """Reicht den KI-Blick asynchron ein (Loop friert nicht ein)."""
    baseUrl, model = _llmEndpoint()
    if not baseUrl or context['ng_screenshot'] is None:
        return False
    payload = _llmHintPayload(context['ng_screenshot'], model)
    if payload is None:
        return False
    jobId = submit(lambda: _askLlmHint(baseUrl, payload))
    if jobId is None:
        return False
    recovery['llmJob'] = jobId
    return True


def _applyLlmHint(hint):
    """Fuehrt eine fertige LLM-Richtungsempfehlung aus."""
    key = _LLM_DIRECTION_KEYS.get((hint or {}).get('direction'))
    if key is None:
        return False
    for _ in range(3):
        press(key)
    logEvent('recoveryLlmHint', {
        'direction': hint['direction'],
        'reason': str(hint.get('reason', ''))[:120],
    })
    return True


def _escalate(context, recovery, level):
    waypoints = context['ng_cave']['waypoints']
    index = waypoints['currentIndex']
    if level == 1:
        # Zufaellige Richtung statt fester Rotation (kein Metronom-Muster)
        direction = random.choice(_SIDESTEPS)
        context['ng_tasksOrchestrator'].reset()
        waypoints['state'] = None
        press(direction)
        logEvent('recoveryNudge', {'direction': direction, 'waypoint': index})
    elif level == 2:
        if _detour(context):
            return
        logEvent('recoveryDetourFailed', {'waypoint': index})
    elif level == 3:
        waypoints['currentIndex'] = (
            getNextArrayIndex(waypoints['items'], index)
            if index is not None else 0
        )
        waypoints['state'] = None
        context['ng_tasksOrchestrator'].reset()
        logEvent('recoverySkip', {
            'from': index, 'to': waypoints['currentIndex']})
    elif level == 4:
        _submitLlmHint(context, recovery)
    elif level == 5:
        target = _startIndex(waypoints['items'])
        waypoints['currentIndex'] = target
        waypoints['state'] = None
        context['ng_tasksOrchestrator'].reset()
        logEvent('recoveryRestart', {'from': index, 'to': target})
    else:
        context['ng_pause'] = True
        logEvent('recoveryGiveUp', {'waypoint': index})


def smartRecovery(context):
    """Loop-Hook: erkennt Stillstand und eskaliert die Rettungsleiter."""
    if not context['ng_cave']['enabled'] or context['ng_pause']:
        return context
    coordinate = context['ng_radar']['coordinate']
    if coordinate is None or any(value is None for value in coordinate):
        return context
    root = context['ng_tasksOrchestrator'].rootTask
    rootName = root.name if root is not None else None
    isMovingPhase = rootName is None or rootName in _MOVING_TASKS
    if context['ng_cave']['isAttackingSomeCreature']:
        isMovingPhase = False
    recovery = context.setdefault('ng_recovery', {})
    # Fertigen LLM-Job auswerten (Richtung nachreichen, nie blockierend)
    jobId = recovery.get('llmJob')
    if jobId is not None:
        hint = poll(jobId)
        if hint is not PENDING:
            recovery['llmJob'] = None
            if hint:
                _applyLlmHint(hint)
    now = monotonic()
    if not isMovingPhase or recovery.get('coordinate') != coordinate:
        recovery['coordinate'] = coordinate
        recovery['since'] = now
        recovery['level'] = 0
        return context
    elapsed = now - recovery.get('since', now)
    level = sum(elapsed > seconds for seconds in STUCK_LEVEL_SECONDS)
    if level <= recovery.get('level', 0):
        return context
    recovery['level'] = level
    _escalate(context, recovery, level)
    return context
