# StatusQuo_Updates/scripts/update.ps1
# Zentraler Updater - wird von update.bat per Invoke-Expression geladen.
# Erwartet im aufrufenden Scope:
#   $proj     - Projektname (z.B. 'VI3DGL', 'FLD-Schichtplanung', 'DRIVE', 'Portal_Union')
#   $root     - Installationspfad (Portal-Wurzel mit index.html, VERSION usw.)
#   $stopBat  - (optional) Stop-Skript-Dateiname; ''=kein Stop; nicht gesetzt=Fallback 'stop.bat'
#   $startBat - (optional) Start-Skript-Dateiname; nicht gesetzt=Fallback 'dgl.bat'
#
# Strategie: Transaktionales Update mit vollständigem ZIP-Snapshot vor jeder
# Änderung. Bei Fehler wird der vorherige Stand automatisch 1:1 wiederhergestellt.
$ErrorActionPreference = 'Stop'
$DistBase = "https://updates.rhino.nrw/rhino/StatusQuo_Updates/raw/branch/main/$proj"

$_stopName  = if ($null -ne $stopBat)  { $stopBat }  else { 'stop.bat' }
$_startName = if ($null -ne $startBat) { $startBat } else { 'dgl.bat' }

# ---------------------------------------------------------------------------
# Hilfsfunktionen
# ---------------------------------------------------------------------------
function Stop-Portal {
    if ($_stopName -ne '') {
        $p = Join-Path $root $_stopName
        if (Test-Path -LiteralPath $p) { & cmd /c "`"$p`""; Start-Sleep -Seconds 2 }
        else { Write-Host "  (Stop-Skript '$_stopName' nicht gefunden — übersprungen)" -ForegroundColor DarkYellow }
    } else { Write-Host "  (Kein Stop-Skript konfiguriert — übersprungen)" -ForegroundColor DarkYellow }
}

function Start-Portal {
    $p = Join-Path $root $_startName
    if (Test-Path -LiteralPath $p) {
        Start-Process -FilePath 'cmd.exe' -ArgumentList "/c `"$p`"" -WindowStyle Hidden
        Write-Host "Server gestartet." -ForegroundColor Green
    } else {
        Write-Host "Start-Skript '$_startName' nicht gefunden — bitte Server manuell starten." -ForegroundColor Yellow
    }
}

# Ausschlussliste: immer .backup/TempUpdate, plus Einträge aus .backupignore
# (große, von Updates NIE berührte Content-Ordner, z.B. _archiv/dokumente).
# Ausgeschlossene Ordner werden weder gesichert noch beim Rollback verändert.
function Get-ExcludeNames {
    param([switch]$ForWipe)
    # WICHTIG: Array strikt als Array aufbauen. 'if (){@(x)}' würde ein
    # 1-Element-Array zu einem Skalar entpacken → '+=' verkettet dann Strings.
    $ex = @('.backup')
    if (-not $ForWipe) { $ex = $ex + 'TempUpdate' }
    $f  = Join-Path $root '.backupignore'
    if (Test-Path -LiteralPath $f) {
        $extra = @(Get-Content -LiteralPath $f | ForEach-Object { $_.Trim() } |
                   Where-Object { $_ -ne '' -and -not $_.StartsWith('#') })
        $ex = $ex + $extra
    }
    return $ex
}

# Vollständiger Snapshot der Portal-Wurzel als ein ZIP (außer .backup/TempUpdate
# und .backupignore-Einträgen). Erfasst alle Code-Unterordner inkl. data/
# → echtes 1:1 des veränderbaren Stands, inkl. später gelöschter Dateien.
function New-Snapshot {
    $ts  = Get-Date -Format 'yyyyMMdd-HHmmss'
    $bk  = Join-Path $root '.backup'
    if (-not (Test-Path -LiteralPath $bk)) { New-Item -ItemType Directory -Path $bk -Force | Out-Null }
    $zip = Join-Path $bk "snapshot-$ts.zip"
    $exclude = @(Get-ExcludeNames)
    $items = Get-ChildItem -LiteralPath $root -Force | Where-Object { $exclude -notcontains $_.Name }
    if (-not $items) { throw "Portal-Wurzel ist leer — nichts zu sichern." }
    Compress-Archive -Path $items.FullName -DestinationPath $zip -CompressionLevel Optimal -Force
    # Integrität prüfen (öffnen + Eintragszahl)
    Add-Type -AssemblyName System.IO.Compression.FileSystem
    $z = [System.IO.Compression.ZipFile]::OpenRead($zip)
    $cnt = $z.Entries.Count
    $z.Dispose()
    if ($cnt -lt 1) { throw "Snapshot ist leer/ungültig." }
    # Aufbewahrung: max. 5 Snapshots
    Get-ChildItem -LiteralPath $bk -Filter 'snapshot-*.zip' | Sort-Object Name -Descending |
        Select-Object -Skip 5 | Remove-Item -Force -ErrorAction SilentlyContinue
    return $zip
}

function Restore-Snapshot {
    param([string]$Zip)
    # Wurzel leeren (außer .backup + .backupignore-Ordner), dann Snapshot zurückspielen.
    # Ausgeschlossene Content-Ordner bleiben unangetastet an Ort und Stelle.
    $ex = @(Get-ExcludeNames -ForWipe)
    Get-ChildItem -LiteralPath $root -Force | Where-Object { $ex -notcontains $_.Name } |
        Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
    Expand-Archive -LiteralPath $Zip -DestinationPath $root -Force
}

# ---------------------------------------------------------------------------
# 1. Versionsvergleich
# ---------------------------------------------------------------------------
$localVer = '0.0.0'
$verFile  = Join-Path $root 'VERSION'
if (Test-Path -LiteralPath $verFile) { $localVer = (Get-Content $verFile -Raw).Trim() }

Write-Host "Prüfe Update für $proj ..."
try {
    $remoteVer = (Invoke-WebRequest -Uri "$DistBase/VERSION" -UseBasicParsing -TimeoutSec 15).Content.Trim()
} catch {
    Write-Host "FEHLER: Update-Server nicht erreichbar. Netzverbindung prüfen." -ForegroundColor Red
    return
}

Write-Host "Lokal:  v$localVer"
Write-Host "Remote: v$remoteVer"

# Nur echtes Upgrade anbieten (kein versehentlicher Downgrade)
$isNewer = $false
try { $isNewer = ([System.Version]$remoteVer -gt [System.Version]$localVer) }
catch { $isNewer = ($remoteVer -ne $localVer) }   # nicht-numerische Version: Fallback
if (-not $isNewer) {
    if ($localVer -eq $remoteVer) { Write-Host "Bereits auf aktuellem Stand. Kein Update nötig." -ForegroundColor Green }
    else { Write-Host "Remote (v$remoteVer) ist nicht neuer als lokal (v$localVer) — kein Update." -ForegroundColor Yellow }
    return
}

$yn = Read-Host "Update von v$localVer auf v$remoteVer installieren? [j/N]"
if ($yn -notin 'j','J','y','Y') { Write-Host "Abgebrochen."; return }

# ---------------------------------------------------------------------------
# 2. Bundles herunterladen (Server läuft noch)
# ---------------------------------------------------------------------------
$tmp = Join-Path $root 'TempUpdate'
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path $tmp | Out-Null

Write-Host "Lade Manifest ..."
$manifest = (Invoke-WebRequest -Uri "$DistBase/MANIFEST.txt" -UseBasicParsing -TimeoutSec 30).Content
$bundles  = ($manifest -split "`n" | Where-Object { $_ -match '^sync-bundle-' } |
             ForEach-Object { ($_ -split '	')[0] } | Sort-Object -Unique)

foreach ($b in $bundles) {
    Write-Host "  $b ..."
    Invoke-WebRequest -Uri "$DistBase/$b" -OutFile (Join-Path $tmp $b) -UseBasicParsing -TimeoutSec 120
}
Write-Host "  sync-entpacken.bat ..."
Invoke-WebRequest -Uri "$DistBase/sync-entpacken.bat" -OutFile (Join-Path $tmp 'sync-entpacken.bat') -UseBasicParsing -TimeoutSec 30

# ---------------------------------------------------------------------------
# 3. Download-Integrität prüfen (SHA256, falls CHECKSUMS.txt vorhanden)
#    Schützt gegen unvollständige/korrupte Downloads — NICHT gegen manipulierte
#    Releases (gleicher Kanal). Authentizität = Release-Disziplin + Tests.
# ---------------------------------------------------------------------------
Write-Host "Verifiziere Download-Integrität ..."
try {
    $chk = (Invoke-WebRequest -Uri "$DistBase/CHECKSUMS.txt" -UseBasicParsing -TimeoutSec 15).Content
    $allOk = $true
    foreach ($line in ($chk -split "`n")) {
        $line = $line.Trim()
        if ($line -eq '' -or $line.StartsWith('#')) { continue }
        $parts = $line -split '\s+', 2
        if ($parts.Count -lt 2) { continue }
        $expected = $parts[0].ToLower()
        $filename = $parts[1].Trim().TrimStart('*')   # sha256sum nutzt evtl. '*' Prefix
        $localPath = Join-Path $tmp $filename
        if (-not (Test-Path -LiteralPath $localPath)) { continue }
        $actual = (Get-FileHash -LiteralPath $localPath -Algorithm SHA256).Hash.ToLower()
        if ($actual -ne $expected) {
            Write-Host "FEHLER: Checksumme stimmt nicht für $filename!" -ForegroundColor Red
            $allOk = $false
        }
    }
    if (-not $allOk) {
        Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
        Write-Host "Update abgebrochen — keine Datei verändert. Bitte Support kontaktieren." -ForegroundColor Red
        return
    }
    Write-Host "  Alle Checksummen OK." -ForegroundColor Green
} catch {
    Write-Host "  (Keine CHECKSUMS.txt — Integritätsprüfung übersprungen)" -ForegroundColor DarkGray
}

# ---------------------------------------------------------------------------
# 4. Server stoppen
# ---------------------------------------------------------------------------
Write-Host "Stoppe Server ..."
Stop-Portal

# ---------------------------------------------------------------------------
# 5. Snapshot (transaktionssicher: ohne gültiges Backup kein Update)
# ---------------------------------------------------------------------------
Write-Host "Erstelle vollständige Sicherung ..."
try {
    $snap = New-Snapshot
    $snapSize = '{0:N1} MB' -f ((Get-Item -LiteralPath $snap).Length / 1MB)
    Write-Host "  Snapshot: $snap ($snapSize)" -ForegroundColor Cyan
} catch {
    Write-Host "FEHLER beim Sichern: $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "Update abgebrochen — keine Datei verändert. Server wird wieder gestartet." -ForegroundColor Yellow
    Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
    Start-Portal
    return
}

# ---------------------------------------------------------------------------
# 6. Update anwenden — bei Fehler automatischer Rollback
# ---------------------------------------------------------------------------
Write-Host "Wende Update an ..."
try {
    & cmd /c "`"$(Join-Path $tmp 'sync-entpacken.bat')`""
    if ($LASTEXITCODE -ne 0) { throw "sync-entpacken.bat meldete Exitcode $LASTEXITCODE" }
    [System.IO.File]::WriteAllText($verFile, ($remoteVer + "`r`n"), [System.Text.Encoding]::UTF8)
} catch {
    Write-Host "FEHLER beim Anwenden: $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "Stelle vorherigen Stand automatisch wieder her ..." -ForegroundColor Yellow
    try {
        Restore-Snapshot -Zip $snap
        Write-Host "  Vorheriger Stand (v$localVer) wiederhergestellt." -ForegroundColor Green
    } catch {
        Write-Host "  AUTO-ROLLBACK FEHLGESCHLAGEN! Bitte rollback.bat doppelklicken." -ForegroundColor Red
    }
    Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
    Start-Portal
    return
}

# ---------------------------------------------------------------------------
# 7. Aufräumen + Start
# ---------------------------------------------------------------------------
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
Write-Host "Version auf v$remoteVer aktualisiert." -ForegroundColor Green
Start-Portal

Write-Host ""
Write-Host "=== Update abgeschlossen ===" -ForegroundColor Green
Write-Host "    Sicherung: $snap" -ForegroundColor DarkGray
Write-Host "    Bei Problemen: rollback.bat doppelklicken (funktioniert auch offline)." -ForegroundColor DarkGray
