param(
    [string]$Repo = (Join-Path $env:USERPROFILE 'TibiaPilotNG'),
    [switch]$RequireTibia
)

$ErrorActionPreference = 'Stop'

function Check([string]$Name, [bool]$Ok, [string]$Detail) {
    [pscustomobject]@{ name = $Name; ok = $Ok; detail = $Detail }
}

$checks = @()
$python = Join-Path $Repo '.venv\Scripts\python.exe'
$checks += Check 'Projekt' (Test-Path (Join-Path $Repo 'pyproject.toml')) $Repo
$checks += Check 'Python-Umgebung' (Test-Path $python) $python
foreach ($relativePath in @(
    'tools\Start-PilotNG-OneClick.ps1',
    'tools\Install-ArduinoToolchain.ps1',
    'tools\Resolve-ArduinoFqbn.ps1',
    'tools\Flash-Leonardo.ps1',
    'tools\Initialize-OBSWebSocket.ps1',
    'tools\Run-Configure-OBS-Capture.ps1',
    'tools\Configure-OBS-Capture.py'
)) {
    $checks += Check "Programmdatei: $relativePath" (Test-Path (Join-Path $Repo $relativePath)) $relativePath
}

$arduinoCli = Get-Command arduino-cli.exe -ErrorAction SilentlyContinue |
    Select-Object -First 1 -ExpandProperty Source
if (!$arduinoCli) {
    # ArduinoSA.CLI (winget) landet unter "Program Files\Arduino CLI" und
    # ist frisch nach der Installation noch nicht im PATH dieser Sitzung.
    # Auch dort und im WinGet-Paketordner nachsehen, sonst meldet der
    # Selbsttest ein installiertes Arduino-CLI faelschlich als fehlend.
    # @() um das Ergebnis, sonst ist $arduinoCandidates bei genau einem
    # Treffer ein String und $arduinoCandidates[0] liefert dessen erstes
    # Zeichen ("C") statt des Pfades.
    $arduinoCandidates = @(
        @(
            (Join-Path $env:ProgramFiles 'Arduino CLI\arduino-cli.exe'),
            (Join-Path ${env:ProgramFiles(x86)} 'Arduino CLI\arduino-cli.exe')
        ) | Where-Object { $_ -and (Test-Path $_) }
    )
    if ($arduinoCandidates.Count -gt 0) {
        $arduinoCli = $arduinoCandidates[0]
    } else {
        $arduinoCli = Get-ChildItem `
            (Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages') `
            -Recurse -Filter 'arduino-cli.exe' -ErrorAction SilentlyContinue |
            Select-Object -First 1 -ExpandProperty FullName
    }
}
$checks += Check 'Arduino CLI' ([bool]$arduinoCli) ([string]$arduinoCli)
if ($arduinoCli) {
    $cores = & $arduinoCli core list --format json | ConvertFrom-Json
    $coreIds = @($cores.platforms | ForEach-Object { $_.id })
    $checks += Check 'Arduino AVR-Core' ($coreIds -contains 'arduino:avr') 'arduino:avr'
    $checks += Check 'SparkFun AVR-Core' ($coreIds -contains 'SparkFun:avr') 'SparkFun:avr'
}

if (Test-Path $python) {
    $previousPreference = $ErrorActionPreference
    $ErrorActionPreference = 'Continue'
    $pipCheck = & $python -m pip check 2>&1
    $pipCheckExit = $LASTEXITCODE
    $ErrorActionPreference = $previousPreference
    $pipCheckDetail = $pipCheck -join ' '
    if ([string]::IsNullOrWhiteSpace($pipCheckDetail)) { $pipCheckDetail = 'pip check' }
    $checks += Check 'Python-Abhaengigkeiten' ($pipCheckExit -eq 0) $pipCheckDetail
}

$tesseract = 'C:\Program Files\Tesseract-OCR\tesseract.exe'
$checks += Check 'Tesseract' (Test-Path $tesseract) $tesseract

$obs = & (Join-Path $Repo 'tools\Find-OBS.ps1')
$checks += Check 'OBS Studio' ([bool]$obs) ([string]$obs)
$obsProfile = Join-Path $env:APPDATA 'obs-studio\basic\profiles\TibiaPilotNG\basic.ini'
$scene = Join-Path $env:APPDATA 'obs-studio\basic\scenes\TibiaPilotNG.json'
$checks += Check 'OBS-Profil' ((Test-Path $obsProfile) -and (Test-Path $scene)) 'TibiaPilotNG'
$obsTemplate = Join-Path $Repo 'installer\obs\TibiaPilotNG.json'
$obsTemplateReady = $false
if (Test-Path $obsTemplate) {
    try {
        $template = Get-Content $obsTemplate -Raw | ConvertFrom-Json
        $source = $template.sources | Where-Object { $_.name -eq 'Tibia' } | Select-Object -First 1
        $obsTemplateReady = ($template.resolution.x -eq 1920) -and
            ($template.resolution.y -eq 1080) -and
            ($source.id -eq 'game_capture') -and
            ($source.settings.capture_cursor -eq $false)
    } catch {}
}
$checks += Check 'OBS-Vorlage 1920x1080' $obsTemplateReady $obsTemplate
$obsWebSocket = Join-Path $env:APPDATA 'obs-studio\plugin_config\obs-websocket\config.json'
$obsWebSocketReady = $false
if (Test-Path $obsWebSocket) {
    try {
        $obsConfig = Get-Content $obsWebSocket -Raw | ConvertFrom-Json
        $obsWebSocketReady = $obsConfig.server_enabled -and
            $obsConfig.auth_required -and
            ($obsConfig.server_port -in 1..65535) -and
            ![string]::IsNullOrWhiteSpace($obsConfig.server_password)
    } catch {}
}
$checks += Check 'OBS-WebSocket' $obsWebSocketReady $obsWebSocket

$ollamaExe = Join-Path $env:LOCALAPPDATA 'Programs\Ollama\ollama.exe'
$checks += Check 'Ollama' ((Test-Path $ollamaExe) -or [bool](Get-Command ollama.exe -ErrorAction SilentlyContinue)) $ollamaExe
$ollamaServer = $false
$ollamaModel = $false
try {
    $tags = Invoke-RestMethod 'http://localhost:11434/api/tags' -TimeoutSec 3
    $ollamaServer = $true
    $ollamaModel = [bool]($tags.models | Where-Object { $_.name.StartsWith('qwen2.5vl:') })
} catch {}
$checks += Check 'Ollama-Server' $ollamaServer 'localhost:11434'
$checks += Check 'Vision-Modell (KI-Blick)' $ollamaModel 'qwen2.5vl:7b'
$ollamaTask = Get-ScheduledTask -TaskName 'OllamaServe' -ErrorAction SilentlyContinue
$checks += Check 'Ollama-Autostart' ($null -ne $ollamaTask) 'OllamaServe'

if (Test-Path $python) {
    & $python -c 'import websocket' 2>$null
    $checks += Check 'OBS-Python-Modul' ($LASTEXITCODE -eq 0) 'websocket-client'
    # pyserial ist die einzige Bruecke zum Arduino - ohne sie kein Input.
    & $python -c 'import serial; serial.Serial' 2>$null
    $checks += Check 'pyserial (Arduino-Bruecke)' ($LASTEXITCODE -eq 0) 'pyserial'
}

# Arduino-Sketch: muss das deutsche Tastaturlayout tippen, sonst kommen
# aus @/y/z falsche Zeichen und der E-Mail-Login scheitert.
$sketch = Join-Path $Repo 'arduino\pilotng_leonardo\pilotng_leonardo.ino'
$sketchOk = (Test-Path $sketch) -and
    (Select-String -Path $sketch -Pattern 'KeyboardLayout_de_DE' -Quiet)
$checks += Check 'Arduino-Sketch (de_DE)' $sketchOk $sketch

# USB-Tarnung: der Chip meldet sich als gewoehnliches Eingabegeraet.
$identity = Join-Path $Repo 'arduino\usb-identity.json'
$identityOk = $false
$identityDetail = $identity
if (Test-Path $identity) {
    try {
        $id = Get-Content $identity -Raw | ConvertFrom-Json
        $identityOk = ($id.vid -match '^0x[0-9A-Fa-f]{4}$') -and
            ($id.pid -match '^0x[0-9A-Fa-f]{4}$') -and
            ![string]::IsNullOrWhiteSpace($id.product)
        $identityDetail = "$($id.vid)/$($id.pid) $($id.product)"
    } catch {}
}
$checks += Check 'USB-Tarnung (VID/PID)' $identityOk $identityDetail

# Deutsche Tastatur aktiv - Voraussetzung fuer das de_DE-Layout des Chips.
$germanOk = $false
$germanDetail = 'de-DE'
try {
    $germanOk = [bool]((Get-WinUserLanguageList) |
        Where-Object { $_.LanguageTag -eq 'de-DE' })
} catch {}
$checks += Check 'Deutsche Tastatur (de-DE)' $germanOk $germanDetail

# Vollstaendige Minimap-Vorlage - ohne sie findet sich der Bot nicht auf
# der Weltkarte wieder.
$vorlage = Join-Path $env:LOCALAPPDATA 'TibiaPilotNG\minimap-vorlage'
$vorlageKacheln = 0
if (Test-Path $vorlage) {
    $vorlageKacheln = @(Get-ChildItem $vorlage -File -Filter 'Minimap_Color_*.png').Count
}
$checks += Check 'Minimap-Vorlage' ($vorlageKacheln -ge 500) "$vorlageKacheln Kacheln"

# Bot-Konfiguration (Wegpunkte, Heilung, Hotkeys) liegt bereit.
# Ueber alle Profil-Slots schauen - das aktive Profil muss nicht in
# Slot 1 liegen.
$config = Join-Path $Repo 'file.json'
$configOk = $false
$configDetail = $config
if (Test-Path $config) {
    try {
        $c = Get-Content $config -Raw | ConvertFrom-Json
        $bestCount = 0
        foreach ($slot in $c._default.PSObject.Properties) {
            $waypoints = $slot.Value.config.ng_cave.waypoints.items
            if ($waypoints.Count -gt $bestCount) { $bestCount = $waypoints.Count }
        }
        $configOk = $bestCount -gt 0
        $configDetail = "$bestCount Wegpunkte"
    } catch {}
}
$checks += Check 'Bot-Konfiguration' $configOk $configDetail

$driver = Join-Path $env:ProgramData 'TibiaPilotNG\usbmmidd\usbmmidd_v2\deviceinstaller64.exe'
$checks += Check 'Virtueller Bildschirm-Treiber' (Test-Path $driver) $driver
$bridge = Get-ScheduledTask -TaskName 'TibiaPilotNG-DesktopBridge' -ErrorAction SilentlyContinue
$checks += Check 'Desktop-Bridge' ($null -ne $bridge) 'TibiaPilotNG-DesktopBridge'
foreach ($taskName in @('TibiaPilotNG-OneClick', 'TibiaPilotNG', 'Tibia-Normal')) {
    $checks += Check "Start-Task: $taskName" ($null -ne (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue)) $taskName
}

$tibia = Join-Path $env:LOCALAPPDATA 'Tibia\Tibia.exe'
$profilePath = Join-Path $env:LOCALAPPDATA 'Tibia\packages\Tibia\conf\clientoptions.json'
$checks += Check 'Tibia Client' (Test-Path $tibia) $tibia
$checks += Check 'Tibia Erststart' (Test-Path $profilePath) $profilePath

# Nicht-Pflicht: Der Tibia-Erststart passiert nach der Installation, und
# die KI ist im Installer bewusst optional (Install-Llm.ps1 laeuft in
# try/catch und darf z. B. ohne Internet fuer das Modell fehlen). Haerter
# zu pruefen hiesse: der gnaedige Ueberspringen-Pfad des Installers liefe
# direkt in einen Selbsttest-Abbruch.
$optional = @('Tibia Erststart', 'Ollama', 'Ollama-Server',
    'Vision-Modell (KI-Blick)', 'Ollama-Autostart')
$required = $checks | Where-Object { $optional -notcontains $_.name }
if ($RequireTibia) { $required = $checks | Where-Object { $_.name -eq 'Tibia Erststart' -or $optional -notcontains $_.name } }
$ok = @($required | Where-Object { -not $_.ok }).Count -eq 0
[ordered]@{
    ok = $ok
    needsFirstTibiaLogin = -not (Test-Path $profilePath)
    checks = $checks
} | ConvertTo-Json -Depth 4

if (!$ok) { exit 1 }
