$ErrorActionPreference = "Stop"

function Invoke-RemotePowerShell {
    param(
        [Parameter(Mandatory = $true)][string]$SshTarget,
        [Parameter(Mandatory = $true)][string]$Script
    )
    $encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Script))
    ssh $SshTarget "powershell -NoProfile -EncodedCommand $encoded"
}

function New-AppArchive {
    $archive = Join-Path $env:TEMP "llm-model-chat-app.zip"
    Remove-Item -Force $archive -ErrorAction SilentlyContinue
    Get-ChildItem -Path "scripts", "tools", "plugins" -Recurse -Directory -Filter "__pycache__" -ErrorAction SilentlyContinue |
        Sort-Object FullName -Descending |
        ForEach-Object {
            Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
        }
    $items = @(
        "config",
        "scripts",
        "tools",
        "registry",
        "plugins",
        "docs",
        "evals",
        "datasets",
        "requirements.txt",
        "requirements-training.txt",
        "README.md"
    )
    Compress-Archive -Path $items -DestinationPath $archive -Force

    Add-Type -AssemblyName System.IO.Compression.FileSystem
    $zip = [System.IO.Compression.ZipFile]::Open($archive, [System.IO.Compression.ZipArchiveMode]::Update)
    try {
        @($zip.Entries | Where-Object { $_.FullName.Replace("\", "/") -match "/__pycache__/" }) |
            ForEach-Object { $_.Delete() }
    } finally {
        $zip.Dispose()
    }

    $preflightReport = "reports/model-chat/preflight.json"
    if (Test-Path -LiteralPath $preflightReport) {
        $zip = [System.IO.Compression.ZipFile]::Open($archive, [System.IO.Compression.ZipArchiveMode]::Update)
        try {
            [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
                $zip,
                (Get-Item -LiteralPath $preflightReport).FullName,
                "reports/model-chat/preflight.json"
            ) | Out-Null
        } finally {
            $zip.Dispose()
        }
    }
    return $archive
}

function Test-AppArchive {
    param([Parameter(Mandatory = $true)][string]$Archive)

    Add-Type -AssemblyName System.IO.Compression.FileSystem
    $requiredEntries = @(
        "config/gpu_profiles.json",
        "scripts/model_chat_server.py",
        "scripts/transformers_plugin_server.py",
        "scripts/common.py",
        "tools/model-chat/index.html",
        "registry/index.json",
        "plugins/1c/rag/profiles.yaml",
        "requirements.txt",
        "requirements-training.txt",
        "README.md"
    )
    if (Test-Path -LiteralPath "reports/model-chat/preflight.json") {
        $requiredEntries += "reports/model-chat/preflight.json"
    }

    $zip = [System.IO.Compression.ZipFile]::OpenRead($Archive)
    try {
        $entryNames = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
        foreach ($entry in $zip.Entries) {
            [void]$entryNames.Add($entry.FullName.Replace("\", "/"))
        }
        $missing = @($requiredEntries | Where-Object { -not $entryNames.Contains($_) })
        if ($missing.Count -gt 0) {
            throw "Archive is missing required entries: $($missing -join ', ')"
        }
        $pycacheEntries = @($entryNames | Where-Object { $_ -match "/__pycache__/" })
        if ($pycacheEntries.Count -gt 0) {
            throw "Archive contains __pycache__ entries: $($pycacheEntries[0])"
        }
        Write-Host "Archive validation passed: $Archive"
    } finally {
        $zip.Dispose()
    }
}

function Sync-AppDirectory {
    param(
        [Parameter(Mandatory = $true)][string]$SshTarget,
        [Parameter(Mandatory = $true)][string]$RemoteArchive,
        [Parameter(Mandatory = $true)][string]$RemoteAppDir,
        [string]$Archive
    )
    if (-not $Archive) {
        $Archive = New-AppArchive
        Test-AppArchive -Archive $Archive
    }
    ssh $SshTarget "cmd /c if not exist C:\ProgramData\LLM mkdir C:\ProgramData\LLM"
    $remoteArchiveForScp = $RemoteArchive.Replace("\", "/")
    scp $Archive "${SshTarget}:$remoteArchiveForScp"
    $unpackScript = @"
New-Item -ItemType Directory -Force '$RemoteAppDir' | Out-Null
Remove-Item -Recurse -Force '$RemoteAppDir\*' -ErrorAction SilentlyContinue
Expand-Archive -Path '$RemoteArchive' -DestinationPath '$RemoteAppDir' -Force
New-Item -ItemType Directory -Force '$RemoteAppDir\reports','$RemoteAppDir\models\incoming' | Out-Null
"@
    Invoke-RemotePowerShell -SshTarget $SshTarget -Script $unpackScript
}
