@echo off
setlocal EnableExtensions DisableDelayedExpansion

if not "%~1"=="" (
  echo ERROR code=arguments_not_supported stage=bootstrap recovery=run-the-zero-argument-command
  exit /b 2
)

set "UNIDESK_CMD_FILE=%~f0"
set "UNIDESK_CONFIG=%USERPROFILE%\.unidesk\provider.yaml"
if not exist "%UNIDESK_CONFIG%" (
  echo ERROR code=config_missing stage=configuration path=%UNIDESK_CONFIG% recovery=write-provider-yaml-first
  exit /b 3
)

echo [1/6] Checking configuration
echo [2/6] Reading Python runtime policy
echo [3/6] Checking reusable Python
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "$p=$env:UNIDESK_CMD_FILE;$c=[System.IO.File]::ReadAllText($p);$m='# POWERSHELL_PAYLOAD';$i=$c.LastIndexOf($m);if($i -lt 0){Write-Host 'ERROR code=bootstrap_payload_missing stage=bootstrap recovery=redownload-installer';exit 4};& ([scriptblock]::Create($c.Substring($i+$m.Length)))"
set "EXIT_CODE=%ERRORLEVEL%"
if not "%EXIT_CODE%"=="0" echo ERROR code=bootstrap_failed stage=bootstrap exitCode=%EXIT_CODE% recovery=read-the-stage-error-above
exit /b %EXIT_CODE%

# POWERSHELL_PAYLOAD
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$configPath = $env:UNIDESK_CONFIG
$bootstrapUrl = "__BOOTSTRAP_URL__"
$runtimeRoot = [System.IO.Path]::Combine($env:USERPROFILE, ".unidesk", "__RUNTIME_ROOT__")
$connectTimeout = __CONNECT_TIMEOUT_SECONDS__
$progressInterval = __PROGRESS_INTERVAL_SECONDS__
$isWindows7 = ([Environment]::OSVersion.Version.Major -eq 6)
if ($isWindows7) {
    $runtime = @{
        version = "__WIN7_VERSION__"
        minimumVersion = "__WIN7_MINIMUM_VERSION__"
        directory = "__WIN7_DIRECTORY__"
        fileName = "__WIN7_FILE_NAME__"
        bytes = __WIN7_BYTES__
        sha256 = "__WIN7_SHA256__"
        sources = @(__WIN7_SOURCES_PS__)
    }
} else {
    $runtime = @{
        version = "__MODERN_VERSION__"
        minimumVersion = "__MODERN_MINIMUM_VERSION__"
        directory = "__MODERN_DIRECTORY__"
        fileName = "__MODERN_FILE_NAME__"
        bytes = __MODERN_BYTES__
        sha256 = "__MODERN_SHA256__"
        sources = @(__MODERN_SOURCES_PS__)
    }
}

function Fail-Stage([string]$Code, [string]$Stage, [string]$Message, [string]$Recovery) {
    Write-Host ("ERROR code={0} stage={1} message={2} recovery={3}" -f $Code, $Stage, $Message, $Recovery)
    exit 10
}

function Get-Sha256([string]$Path) {
    $stream = [System.IO.File]::OpenRead($Path)
    try {
        $hasher = New-Object System.Security.Cryptography.SHA256Managed
        try {
            return ([BitConverter]::ToString($hasher.ComputeHash($stream))).Replace("-", "").ToLowerInvariant()
        } finally {
            $hasher.Dispose()
        }
    } finally {
        $stream.Dispose()
    }
}

function Test-Python([string]$Executable, [string[]]$Prefix, [string]$MinimumVersion) {
    try {
        $parts = $MinimumVersion.Split(".")
        $major = [int]$parts[0]
        $minor = [int]$parts[1]
        $code = "import sys,tkinter; print(sys.executable); raise SystemExit(0 if sys.version_info >= ($major,$minor) else 1)"
        $output = & $Executable @Prefix -c $code 2>&1
        if ($LASTEXITCODE -ne 0) { return $null }
        $candidate = "$($output | Select-Object -Last 1)".Trim()
        if ([System.IO.File]::Exists($candidate)) { return $candidate }
    } catch {}
    return $null
}

function Find-Python([string]$MinimumVersion) {
    $candidates = @(
        @{ executable = "py.exe"; prefix = @("-$MinimumVersion") },
        @{ executable = "python.exe"; prefix = @() },
        @{ executable = "python3.exe"; prefix = @() },
        @{ executable = [System.IO.Path]::Combine($env:ProgramData, "miniconda3", "python.exe"); prefix = @() }
    )
    foreach ($candidate in $candidates) {
        $path = Test-Python $candidate.executable $candidate.prefix $MinimumVersion
        if ($path) {
            Write-Host ("[reuse] system Python {0}" -f $path)
            return $path
        }
    }
    return $null
}

function Read-PythonRuntimeMode {
    $lines = @(Get-Content -LiteralPath $configPath | Where-Object { $_ -match "^\s*pythonRuntime\s*:" })
    if ($lines.Count -eq 0) { return "auto" }
    if ($lines.Count -gt 1) {
        Fail-Stage "python_runtime_duplicate" "configuration" "pythonRuntime is declared more than once" "keep one pythonRuntime value in provider.yaml"
    }
    $match = [regex]::Match(
        [string]$lines[0],
        "^\s*pythonRuntime\s*:\s*['`"]?(auto|system|private)['`"]?\s*(?:#.*)?$",
        [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
    )
    if (-not $match.Success) {
        Fail-Stage "python_runtime_invalid" "configuration" "pythonRuntime must be auto, system, or private" "edit provider.yaml and retry"
    }
    return $match.Groups[1].Value.ToLowerInvariant()
}

function Probe-Source($Source) {
    $watch = [Diagnostics.Stopwatch]::StartNew()
    try {
        $request = [Net.WebRequest]::Create($Source.url)
        $request.Method = "HEAD"
        $request.Timeout = $connectTimeout * 1000
        $request.AllowAutoRedirect = $true
        $response = $request.GetResponse()
        try {
            $watch.Stop()
            Write-Host ("[network] source={0} status=ok latencyMs={1}" -f $Source.id, $watch.ElapsedMilliseconds)
            return @{ source = $Source; latency = $watch.ElapsedMilliseconds; length = $response.ContentLength }
        } finally {
            $response.Close()
        }
    } catch {
        $watch.Stop()
        Write-Host ("[network] source={0} status=unavailable elapsedMs={1}" -f $Source.id, $watch.ElapsedMilliseconds)
    }
    return $null
}

function Select-Source($Sources) {
    $best = $null
    foreach ($source in $Sources) {
        $probe = Probe-Source $source
        if ($probe -and ($null -eq $best -or [int64]$probe.latency -lt [int64]$best.latency)) {
            $best = $probe
        }
    }
    return $best
}

function Download-File([string]$Url, [string]$Path, [string]$Label, [string]$SourceId) {
    $request = [Net.WebRequest]::Create($Url)
    $request.Method = "GET"
    $request.Timeout = $connectTimeout * 1000
    $request.ReadWriteTimeout = 30000
    $request.AllowAutoRedirect = $true
    $response = $request.GetResponse()
    $total = $response.ContentLength
    $input = $response.GetResponseStream()
    $output = [System.IO.File]::Open(
        $Path,
        [System.IO.FileMode]::Create,
        [System.IO.FileAccess]::Write,
        [System.IO.FileShare]::None
    )
    $buffer = New-Object byte[] (262144)
    $done = [int64]0
    $last = [Diagnostics.Stopwatch]::StartNew()
    $started = [Diagnostics.Stopwatch]::StartNew()
    try {
        while (($read = $input.Read($buffer, 0, $buffer.Length)) -gt 0) {
            $output.Write($buffer, 0, $read)
            $done += $read
            if ($last.Elapsed.TotalSeconds -ge $progressInterval) {
                $seconds = [Math]::Max(0.001, $started.Elapsed.TotalSeconds)
                $speed = $done / $seconds
                if ($total -gt 0) {
                    $percent = $done * 100.0 / $total
                    $eta = [Math]::Max(0, [int](($total - $done) / $speed + 0.5))
                    $size = "{0:N1}/{1:N1} MiB" -f ($done / 1048576.0), ($total / 1048576.0)
                    Write-Host ("`r[download] {0} {1:N1}% {2} speed={3:N1} KiB/s elapsed={4}s eta={5}s source={6}" -f $Label, $percent, $size, ($speed / 1024.0), [int]$started.Elapsed.TotalSeconds, $eta, $SourceId) -NoNewline
                } else {
                    Write-Host ("`r[download] {0} bytes={1} speed={2:N1} KiB/s elapsed={3}s eta=unknown source={4}" -f $Label, $done, ($speed / 1024.0), [int]$started.Elapsed.TotalSeconds, $SourceId) -NoNewline
                }
                $last.Restart()
            }
        }
    } finally {
        $output.Close()
        $input.Close()
        $response.Close()
    }
    $seconds = [Math]::Max(0.001, $started.Elapsed.TotalSeconds)
    $speed = $done / $seconds
    if ($total -gt 0) {
        $percent = $done * 100.0 / $total
        $eta = [Math]::Max(0, [int](($total - $done) / $speed + 0.5))
        $size = "{0:N1}/{1:N1} MiB" -f ($done / 1048576.0), ($total / 1048576.0)
        Write-Host ("`r[download] {0} {1:N1}% {2} speed={3:N1} KiB/s elapsed={4}s eta={5}s source={6}" -f $Label, $percent, $size, ($speed / 1024.0), [int]$started.Elapsed.TotalSeconds, $eta, $SourceId)
    } else {
        Write-Host ("`r[download] {0} bytes={1} speed={2:N1} KiB/s elapsed={3}s eta=unknown source={4}" -f $Label, $done, ($speed / 1024.0), [int]$started.Elapsed.TotalSeconds, $SourceId)
    }
}

function Install-PrivatePython {
    $source = Select-Source $runtime.sources
    if (-not $source) { Fail-Stage "runtime_source_unreachable" "runtime-download" "no runtime source is reachable" "check network or retry" }
    Write-Host ("[network] selected={0} pipIndex={1}" -f $source.source.id, $source.source.pipIndexUrl)
    $runtimeDirectory = [System.IO.Path]::Combine($runtimeRoot, $runtime.directory)
    $installer = [System.IO.Path]::Combine($env:TEMP, $runtime.fileName)
    $downloaded = $false
    $orderedSources = @($source.source) + @($runtime.sources | Where-Object { $_.id -ne $source.source.id })
    foreach ($attemptSource in $orderedSources) {
        try {
            if ([System.IO.File]::Exists($installer)) { Remove-Item -Force $installer }
            Write-Host ("[4/6] Downloading private Python {0} from {1}" -f $runtime.version, $attemptSource.id)
            Download-File $attemptSource.url $installer "python-$($runtime.version)" $attemptSource.id
            if ((Get-Item $installer).Length -ne [int64]$runtime.bytes -or (Get-Sha256 $installer) -ne $runtime.sha256) {
                throw "bytes or SHA-256 mismatch"
            }
            $selectedSource = $attemptSource
            $downloaded = $true
            break
        } catch {
            Write-Host ("[network] source={0} failed={1}; trying next source" -f $attemptSource.id, $_.Exception.Message)
        }
    }
    if (-not $downloaded) { Fail-Stage "runtime_integrity_failed" "runtime-download" "all runtime sources failed integrity or download" "retry the command" }
    New-Item -ItemType Directory -Force -Path $runtimeRoot | Out-Null
    Write-Host ("[4/6] Installing private Python into {0}" -f $runtimeDirectory)
    $process = Start-Process -FilePath $installer -ArgumentList ("/quiet InstallAllUsers=0 TargetDir=`"{0}`" Include_launcher=0 AssociateFiles=0 PrependPath=0 Shortcuts=0 Include_doc=0 Include_test=0 Include_tcltk=1 Include_pip=1 Include_lib=1 Include_exe=1" -f $runtimeDirectory) -PassThru
    $elapsed = [Diagnostics.Stopwatch]::StartNew()
    while (-not $process.HasExited) {
        Write-Host ("[install] Python {0} status=running elapsed={1}s" -f $runtime.version, [int]$elapsed.Elapsed.TotalSeconds)
        Start-Sleep -Seconds 1
        $process.Refresh()
    }
    if ($process.ExitCode -ne 0) { Fail-Stage "runtime_install_failed" "runtime-install" "installer exit code $($process.ExitCode)" "remove the incomplete runtime directory and retry" }
    Remove-Item -Force $installer -ErrorAction SilentlyContinue
    $python = Test-Python ([System.IO.Path]::Combine($runtimeDirectory, "python.exe")) @() $runtime.minimumVersion
    if (-not $python) { Fail-Stage "runtime_validation_failed" "runtime-install" "installed Python failed version or tkinter validation" "remove the incomplete runtime directory and retry" }
    $env:UNIDESK_PIP_INDEX = $selectedSource.pipIndexUrl
    return $python
}

try {
    [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072
    $pythonRuntimeMode = Read-PythonRuntimeMode
    Write-Host ("[config] pythonRuntime={0}" -f $pythonRuntimeMode)
    $python = $null
    if ($pythonRuntimeMode -ne "private") {
        $python = Find-Python $runtime.minimumVersion
    }
    if (-not $python -and $pythonRuntimeMode -ne "system") {
        $python = Test-Python ([System.IO.Path]::Combine($runtimeRoot, $runtime.directory, "python.exe")) @() $runtime.minimumVersion
        if ($python) { Write-Host ("[reuse] private Python {0}" -f $python) }
    }
    if (-not $python -and $pythonRuntimeMode -eq "system") {
        Fail-Stage "system_python_unavailable" "runtime-selection" "system Python does not satisfy version or tkinter requirements" "set pythonRuntime: private in provider.yaml and retry"
    }
    if (-not $python) { $python = Install-PrivatePython }
    if (-not $env:UNIDESK_PIP_INDEX) {
        $pipSource = Select-Source $runtime.sources
        if ($pipSource) { $env:UNIDESK_PIP_INDEX = $pipSource.source.pipIndexUrl }
    }
    Write-Host "[5/6] Preparing Provider installer"
    $helper = [System.IO.Path]::Combine($env:TEMP, "unideskcprovider-install-$PID.py")
    Download-File $bootstrapUrl $helper "installer-helper" "apps.hwpod.com"
    & $python $helper
    $exitCode = $LASTEXITCODE
    Remove-Item -Force $helper -ErrorAction SilentlyContinue
    exit $exitCode
} catch {
    Fail-Stage "bootstrap_failed" "bootstrap" $_.Exception.Message "read the stage above and retry"
}
