param(
    [Parameter(Mandatory = $true)][string]$DownloadUrl,
    [Parameter(Mandatory = $true)][string]$AppDir,
    [Parameter(Mandatory = $true)][string]$ExePath,
    [Parameter(Mandatory = $true)][int]$ProcessId,
    [string]$RemoteVersion = "",
    [string]$LogPath = ""
)

$ErrorActionPreference = "Stop"

$script:UpdateForm = $null
$script:UpdateStatusLabel = $null
$script:UpdateProgressBar = $null
$script:UpdatePercentLabel = $null

function Show-UpdateWindow {
    try {
        Add-Type -AssemblyName System.Windows.Forms
        Add-Type -AssemblyName System.Drawing

        $form = New-Object System.Windows.Forms.Form
        $form.Text = "Updating CitiFox ERP App"
        $form.Width = 460
        $form.Height = 170
        $form.StartPosition = "CenterScreen"
        $form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
        $form.MaximizeBox = $false
        $form.MinimizeBox = $false
        $form.TopMost = $true

        $titleLabel = New-Object System.Windows.Forms.Label
        $titleLabel.Text = "Updating app..."
        $titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 12, [System.Drawing.FontStyle]::Bold)
        $titleLabel.AutoSize = $true
        $titleLabel.Left = 22
        $titleLabel.Top = 18
        $form.Controls.Add($titleLabel)

        $statusLabel = New-Object System.Windows.Forms.Label
        $statusLabel.Text = "Preparing update..."
        $statusLabel.Font = New-Object System.Drawing.Font("Segoe UI", 9)
        $statusLabel.Left = 24
        $statusLabel.Top = 58
        $statusLabel.Width = 400
        $statusLabel.Height = 24
        $form.Controls.Add($statusLabel)

        $percentLabel = New-Object System.Windows.Forms.Label
        $percentLabel.Text = "0%"
        $percentLabel.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
        $percentLabel.Left = 384
        $percentLabel.Top = 58
        $percentLabel.Width = 42
        $percentLabel.Height = 24
        $percentLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight
        $form.Controls.Add($percentLabel)

        $progressBar = New-Object System.Windows.Forms.ProgressBar
        $progressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Continuous
        $progressBar.Minimum = 0
        $progressBar.Maximum = 100
        $progressBar.Value = 0
        $progressBar.Left = 24
        $progressBar.Top = 92
        $progressBar.Width = 395
        $progressBar.Height = 18
        $form.Controls.Add($progressBar)

        $script:UpdateForm = $form
        $script:UpdateStatusLabel = $statusLabel
        $script:UpdateProgressBar = $progressBar
        $script:UpdatePercentLabel = $percentLabel
        $form.Show()
        [System.Windows.Forms.Application]::DoEvents()
    }
    catch {
        $script:UpdateForm = $null
        $script:UpdateStatusLabel = $null
        $script:UpdateProgressBar = $null
        $script:UpdatePercentLabel = $null
    }
}

function Set-UpdateWindowStatus($message, $percent = $null) {
    try {
        if ($script:UpdateStatusLabel -ne $null) {
            $script:UpdateStatusLabel.Text = [string]$message
        }
        if ($percent -ne $null) {
            $normalizedPercent = [Math]::Max(0, [Math]::Min(100, [int]$percent))
            if ($script:UpdateProgressBar -ne $null) {
                $script:UpdateProgressBar.Value = $normalizedPercent
            }
            if ($script:UpdatePercentLabel -ne $null) {
                $script:UpdatePercentLabel.Text = "$normalizedPercent%"
            }
        }
        if ($script:UpdateForm -ne $null) {
            $script:UpdateForm.Refresh()
            [System.Windows.Forms.Application]::DoEvents()
        }
    }
    catch {
    }
}

function Close-UpdateWindow {
    try {
        if ($script:UpdateForm -ne $null) {
            $script:UpdateForm.Close()
            $script:UpdateForm.Dispose()
        }
    }
    catch {
    }
    finally {
        $script:UpdateForm = $null
        $script:UpdateStatusLabel = $null
        $script:UpdateProgressBar = $null
        $script:UpdatePercentLabel = $null
    }
}

function Download-FileWithProgress($url, $destinationPath) {
    $request = [System.Net.HttpWebRequest]::Create($url)
    $request.UserAgent = "CitiFoxERPAppUpdater/1.0"
    $response = $request.GetResponse()
    try {
        $totalBytes = [int64]$response.ContentLength
        $inputStream = $response.GetResponseStream()
        $outputStream = [System.IO.File]::Open($destinationPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None)
        try {
            $buffer = New-Object byte[] (1024 * 128)
            $downloadedBytes = [int64]0
            while ($true) {
                $readBytes = $inputStream.Read($buffer, 0, $buffer.Length)
                if ($readBytes -le 0) {
                    break
                }
                $outputStream.Write($buffer, 0, $readBytes)
                $downloadedBytes += $readBytes
                if ($totalBytes -gt 0) {
                    $downloadPercent = [int](10 + (($downloadedBytes * 45) / $totalBytes))
                    Set-UpdateWindowStatus "Downloading update package..." $downloadPercent
                }
                else {
                    $downloadPercent = [int][Math]::Min(54, 10 + (($downloadedBytes / 1MB) * 4))
                    Set-UpdateWindowStatus "Downloading update package..." $downloadPercent
                }
            }
        }
        finally {
            if ($outputStream -ne $null) {
                $outputStream.Close()
            }
            if ($inputStream -ne $null) {
                $inputStream.Close()
            }
        }
    }
    finally {
        if ($response -ne $null) {
            $response.Close()
        }
    }
}

function Write-UpdateLog($message) {
    $line = "[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $message
    if ($LogPath) {
        $logDir = Split-Path -Parent $LogPath
        if ($logDir -and -not (Test-Path $logDir)) {
            New-Item -ItemType Directory -Force -Path $logDir | Out-Null
        }
        Add-Content -LiteralPath $LogPath -Value $line -Encoding UTF8
    }
    Write-Host $line
}

function Copy-DirectoryContents($sourceDir, $targetDir, $relativePath = "") {
    if (-not (Test-Path $targetDir)) {
        New-Item -ItemType Directory -Force -Path $targetDir | Out-Null
    }
    Get-ChildItem -LiteralPath $sourceDir -Force | ForEach-Object {
        $target = Join-Path $targetDir $_.Name
        $childRelativePath = if ($relativePath) { Join-Path $relativePath $_.Name } else { $_.Name }
        if ($_.PSIsContainer) {
            Copy-DirectoryContents $_.FullName $target $childRelativePath
        }
        else {
            if ($childRelativePath -like "settings\*" -and (Test-Path $target)) {
                Write-UpdateLog "Keeping existing setting file: $childRelativePath"
                return
            }
            Copy-Item -LiteralPath $_.FullName -Destination $target -Force
        }
    }
}

$appDirPath = [System.IO.Path]::GetFullPath($AppDir)
$exePathValue = [System.IO.Path]::GetFullPath($ExePath)
if (-not (Test-Path $appDirPath)) {
    throw "App directory does not exist: $appDirPath"
}
if (-not (Test-Path $exePathValue)) {
    throw "Executable does not exist: $exePathValue"
}
if (-not $exePathValue.StartsWith($appDirPath, [System.StringComparison]::OrdinalIgnoreCase)) {
    throw "Executable must be inside app directory."
}

$workRoot = Join-Path $env:TEMP ("CitiFoxERPUpdate_" + [System.Guid]::NewGuid().ToString("N"))
$zipPath = Join-Path $workRoot "app_update.zip"
$extractDir = Join-Path $workRoot "extract"

try {
    New-Item -ItemType Directory -Force -Path $workRoot | Out-Null
    Show-UpdateWindow
    Set-UpdateWindowStatus "Waiting for the app to close..." 5
    Write-UpdateLog "Starting update. Target version: $RemoteVersion"
    Write-UpdateLog "Waiting for app process $ProcessId to exit."
    try {
        $process = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue
        if ($process) {
            Wait-Process -Id $ProcessId -Timeout 60 -ErrorAction SilentlyContinue
            $process = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue
            if ($process) {
                throw "App process did not exit within 60 seconds."
            }
        }
    }
    catch {
        Write-UpdateLog "Process wait warning: $($_.Exception.Message)"
    }

    Set-UpdateWindowStatus "Downloading update package..." 10
    Write-UpdateLog "Downloading package: $DownloadUrl"
    Download-FileWithProgress $DownloadUrl $zipPath
    if (-not (Test-Path $zipPath)) {
        throw "Download failed: $zipPath"
    }

    Set-UpdateWindowStatus "Extracting update package..." 58
    Write-UpdateLog "Extracting package."
    Expand-Archive -LiteralPath $zipPath -DestinationPath $extractDir -Force

    $payloadDir = Join-Path $extractDir "SunnyERPApp"
    if (-not (Test-Path $payloadDir)) {
        $directories = @(Get-ChildItem -LiteralPath $extractDir -Directory)
        if ($directories.Count -eq 1) {
            $payloadDir = $directories[0].FullName
        }
        else {
            $payloadDir = $extractDir
        }
    }
    $payloadExe = Join-Path $payloadDir ([System.IO.Path]::GetFileName($exePathValue))
    if (-not (Test-Path $payloadExe)) {
        $firstExe = Get-ChildItem -LiteralPath $payloadDir -Filter "*.exe" -File -Recurse | Select-Object -First 1
        if (-not $firstExe) {
            throw "Extracted package does not contain an executable."
        }
    }

    Set-UpdateWindowStatus "Installing files..." 75
    Write-UpdateLog "Copying update into app directory: $appDirPath"
    Copy-DirectoryContents $payloadDir $appDirPath

    Set-UpdateWindowStatus "Restarting app..." 96
    Write-UpdateLog "Restarting app: $exePathValue"
    Start-Process -FilePath $exePathValue -WorkingDirectory $appDirPath
    Set-UpdateWindowStatus "Update completed." 100
    Start-Sleep -Milliseconds 700
    Write-UpdateLog "Update completed."
}
catch {
    Write-UpdateLog "Update failed: $($_.Exception.Message)"
    throw
}
finally {
    Close-UpdateWindow
    try {
        if (Test-Path $workRoot) {
            Remove-Item -LiteralPath $workRoot -Recurse -Force
        }
    }
    catch {
    }
}
