[CmdletBinding()]
param(
    [string]$InstallRoot = (Join-Path $env:LOCALAPPDATA "Programs\BMCLI"),
    [string]$BaseUrl = "https://download.busmust.com/bmcli",
    [string]$Version,
    [string]$PackagePath,
    [string]$ExpectedSha256,
    [int]$WaitForPid = 0,
    [switch]$NoPath,
    [switch]$NoAI
)

$ErrorActionPreference = "Stop"
$InstallRoot = [IO.Path]::GetFullPath($InstallRoot)
$platform = "windows-x86"
$tempRoot = Join-Path ([IO.Path]::GetTempPath()) ("bmcli-install-" + [guid]::NewGuid().ToString("N"))
$archivePath = Join-Path $tempRoot "bmcli.zip"
$extractRoot = Join-Path $tempRoot "extract"

function Assert-ProductVersion([string]$Value) {
    if ($Value -notmatch '^\d+\.\d+\.\d+$') {
        throw "Invalid BMCLI version '$Value'; expected X.Y.Z"
    }
}

function Add-UserPath([string]$Directory) {
    $current = [Environment]::GetEnvironmentVariable("Path", "User")
    $parts = @($current -split ';' | Where-Object { $_ })
    if ($parts -notcontains $Directory) {
        $updated = if ([string]::IsNullOrEmpty($current)) {
            $Directory
        } elseif ($current.EndsWith(';')) {
            $current + $Directory
        } else {
            $current + ';' + $Directory
        }
        [Environment]::SetEnvironmentVariable("Path", $updated, "User")
    }
    if (($env:Path -split ';') -notcontains $Directory) {
        $env:Path = "$Directory;$env:Path"
    }
}

function Expand-VerifiedZip([string]$Archive, [string]$Destination) {
    Add-Type -AssemblyName System.IO.Compression.FileSystem
    $root = [IO.Path]::GetFullPath($Destination).TrimEnd('\') + '\'
    $zip = [IO.Compression.ZipFile]::OpenRead($Archive)
    try {
        foreach ($entry in $zip.Entries) {
            $target = [IO.Path]::GetFullPath((Join-Path $Destination $entry.FullName))
            if (-not $target.StartsWith($root, [StringComparison]::OrdinalIgnoreCase)) {
                throw "Package contains an unsafe path: $($entry.FullName)"
            }
        }
    } finally {
        $zip.Dispose()
    }
    Expand-Archive -LiteralPath $Archive -DestinationPath $Destination
}

function Assert-Payload([string]$Payload, [string]$ExpectedVersion) {
    $binary = Join-Path $Payload "bin\bmcli.exe"
    $versionFile = Join-Path $Payload "VERSION"
    $buildInfoFile = Join-Path $Payload "BUILD_INFO.json"
    foreach ($required in @($binary, $versionFile, $buildInfoFile, (Join-Path $Payload "share\bmcli"))) {
        if (-not (Test-Path -LiteralPath $required)) {
            throw "Invalid BMCLI package layout; missing $required"
        }
    }
    $payloadVersion = (Get-Content -LiteralPath $versionFile -Raw).Trim()
    Assert-ProductVersion $payloadVersion
    if ($payloadVersion -ne $ExpectedVersion) {
        throw "Package VERSION '$payloadVersion' does not match requested version '$ExpectedVersion'"
    }
    $buildInfo = Get-Content -LiteralPath $buildInfoFile -Raw | ConvertFrom-Json
    if ([string]$buildInfo.version -ne $ExpectedVersion) {
        throw "BUILD_INFO version does not match package version"
    }
    $binaryInfo = & $binary version --format=json | ConvertFrom-Json
    if ($LASTEXITCODE -ne 0 -or [string]$binaryInfo.version -ne $ExpectedVersion -or
        [string]$binaryInfo.build_number -ne [string]$buildInfo.build_number -or
        [string]$binaryInfo.git_commit -ne [string]$buildInfo.git_commit) {
        throw "BMCLI binary metadata does not match VERSION and BUILD_INFO"
    }
}

try {
    New-Item -ItemType Directory -Force $tempRoot, $extractRoot | Out-Null

    if ($PackagePath) {
        $resolvedPackage = (Resolve-Path -LiteralPath $PackagePath).Path
        Copy-Item -LiteralPath $resolvedPackage -Destination $archivePath
        if (-not $Version) {
            $name = [IO.Path]::GetFileNameWithoutExtension($resolvedPackage)
            if ($name -notmatch '^bmcli-(\d+\.\d+\.\d+)-windows-x86$') {
                throw "Cannot infer a valid product version from package name: $name"
            }
            $Version = $Matches[1]
        }
    } else {
        if ($Version) { Assert-ProductVersion $Version }
        $manifestUrl = if ($Version) {
            "$BaseUrl/releases/$Version/manifest.json"
        } else {
            "$BaseUrl/releases/latest/manifest.json"
        }
        $manifest = Invoke-RestMethod -Uri $manifestUrl
        if ($manifest.schema_version -ne 1) { throw "Unsupported manifest schema" }
        $manifestVersion = [string]$manifest.version
        Assert-ProductVersion $manifestVersion
        if ($Version -and $Version -ne $manifestVersion) {
            throw "Manifest version '$manifestVersion' does not match requested version '$Version'"
        }
        $Version = $manifestVersion
        $artifact = $manifest.artifacts.$platform
        if (-not $artifact) { throw "No $platform artifact in release $Version" }
        $ExpectedSha256 = [string]$artifact.sha256
        Invoke-WebRequest -Uri ([string]$artifact.url) -OutFile $archivePath
    }

    Assert-ProductVersion $Version
    if ($ExpectedSha256) {
        if ($ExpectedSha256 -notmatch '^[0-9A-Fa-f]{64}$') { throw "Invalid expected SHA-256" }
        $actual = (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash
        if ($actual -ne $ExpectedSha256) {
            throw "SHA-256 mismatch: expected $ExpectedSha256, got $actual"
        }
    } elseif (-not $PackagePath) {
        throw "Release manifest did not provide SHA-256"
    }

    Expand-VerifiedZip $archivePath $extractRoot
    $payload = Join-Path $extractRoot "bmcli-$Version-windows-x86"
    Assert-Payload $payload $Version

    $versionsRoot = Join-Path $InstallRoot "versions"
    $versionRoot = Join-Path $versionsRoot $Version
    $stagingRoot = Join-Path $versionsRoot (".$Version.new-" + [guid]::NewGuid().ToString("N"))
    $binRoot = Join-Path $InstallRoot "bin"
    $shareRoot = Join-Path $InstallRoot "share"
    $backupRoot = Join-Path $tempRoot "previous-active"
    New-Item -ItemType Directory -Force $versionsRoot, $backupRoot | Out-Null
    Copy-Item -LiteralPath $payload -Destination $stagingRoot -Recurse

    if ($WaitForPid -gt 0 -and (Get-Process -Id $WaitForPid -ErrorAction SilentlyContinue)) {
        Wait-Process -Id $WaitForPid -ErrorAction SilentlyContinue
    }
    Remove-Item -LiteralPath (Join-Path $InstallRoot ".install-complete") -Force `
        -ErrorAction SilentlyContinue

    foreach ($item in @("bin", "share", "BUILD_INFO.json", "LICENSE.txt", "README.md", "current-version")) {
        $source = Join-Path $InstallRoot $item
        if (Test-Path -LiteralPath $source) {
            Copy-Item -LiteralPath $source -Destination (Join-Path $backupRoot $item) -Recurse
        }
    }
    try {
        Remove-Item -LiteralPath $versionRoot -Recurse -Force -ErrorAction SilentlyContinue
        Move-Item -LiteralPath $stagingRoot -Destination $versionRoot
        Remove-Item -LiteralPath $binRoot, $shareRoot -Recurse -Force -ErrorAction SilentlyContinue
        New-Item -ItemType Directory -Force $binRoot | Out-Null
        Copy-Item -Path (Join-Path $versionRoot "bin\*") -Destination $binRoot -Recurse
        Copy-Item -LiteralPath (Join-Path $versionRoot "share") -Destination $InstallRoot -Recurse
        Copy-Item -LiteralPath (Join-Path $versionRoot "BUILD_INFO.json") `
            -Destination (Join-Path $InstallRoot "BUILD_INFO.json") -Force
        Copy-Item -LiteralPath (Join-Path $versionRoot "LICENSE.txt") `
            -Destination (Join-Path $InstallRoot "LICENSE.txt") -Force
        Copy-Item -LiteralPath (Join-Path $versionRoot "README.md") `
            -Destination (Join-Path $InstallRoot "README.md") -Force
        Set-Content -LiteralPath (Join-Path $InstallRoot "current-version") -Value $Version -Encoding ascii
    } catch {
        foreach ($item in @("bin", "share", "BUILD_INFO.json", "LICENSE.txt", "README.md", "current-version")) {
            Remove-Item -LiteralPath (Join-Path $InstallRoot $item) -Recurse -Force -ErrorAction SilentlyContinue
            $backup = Join-Path $backupRoot $item
            if (Test-Path -LiteralPath $backup) {
                Copy-Item -LiteralPath $backup -Destination (Join-Path $InstallRoot $item) -Recurse
            }
        }
        throw
    }

    if (-not $NoPath) { Add-UserPath $binRoot }
    if (-not $NoAI) {
        & (Join-Path $binRoot "bmcli.exe") ai setup
        if ($LASTEXITCODE -ne 0) { throw "BMCLI installed, but AI skill setup failed" }
    }
    Set-Content -LiteralPath (Join-Path $InstallRoot ".install-complete") `
        -Value $Version -Encoding ascii

    Write-Output "BMCLI $Version installed to $InstallRoot"
    Write-Output "Run: bmcli doctor"
} catch {
    $failureLog = Join-Path ([IO.Path]::GetTempPath()) "bmcli-update-error.log"
    $details = @(
        [string]$_
        [string]$_.InvocationInfo.PositionMessage
        [string]$_.ScriptStackTrace
    ) -join "`r`n"
    Set-Content -LiteralPath $failureLog -Value $details -Encoding utf8
    throw
} finally {
    Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
}
