[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)][ValidateSet("check", "download")][string]$Mode,
    [string]$Cid,
    [string]$CurrentVersion,
    [string]$InfoHex,
    [string]$FirmwarePath,
    [Parameter(Mandatory = $true)][string]$ResultPath,
    [string]$BaseUrl = "",
    [string]$CgiUrl = ""
)

$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
if (-not $BaseUrl) {
    $BaseUrl = if ($env:BMCLI_FIRMWARE_BASE_URL) { $env:BMCLI_FIRMWARE_BASE_URL } `
        else { "https://download.busmust.com/files" }
}
if (-not $CgiUrl) {
    $CgiUrl = if ($env:BMCLI_FIRMWARE_CGI_URL) { $env:BMCLI_FIRMWARE_CGI_URL } `
        else { "https://download.busmust.com/cgi/DownloadFirmware.cgi" }
}

function Parse-FirmwareVersion([string]$Value) {
    if ($Value -notmatch "^(\d+)\.(\d+)\.(\d+)\.(\d+)$") {
        throw "Invalid firmware version '$Value'"
    }
    $parts = @([int]$Matches[1], [int]$Matches[2], [int]$Matches[3], [int]$Matches[4])
    if (@($parts | Where-Object { $_ -lt 0 -or $_ -gt 255 }).Count -ne 0) {
        throw "Firmware version components must be in 0..255"
    }
    return $parts
}

function Compare-FirmwareVersion([int[]]$Left, [int[]]$Right) {
    for ($i = 0; $i -lt 4; $i++) {
        if ($Left[$i] -lt $Right[$i]) { return -1 }
        if ($Left[$i] -gt $Right[$i]) { return 1 }
    }
    return 0
}

if ($Mode -eq "check") {
    if ($Cid -notmatch "^[0-9A-Fa-f]{4}$") { throw "Invalid firmware CID" }
    $current = Parse-FirmwareVersion $CurrentVersion
    $Cid = $Cid.ToUpperInvariant()
    $changelistUrl = "$($BaseUrl.TrimEnd('/'))/Firmware_${Cid}_Changelist.txt"
    $changelistPath = [IO.Path]::GetTempFileName()
    try {
        Invoke-WebRequest -Uri $changelistUrl -OutFile $changelistPath
        $changelist = Get-Content -Raw -LiteralPath $changelistPath
    } finally {
        Remove-Item -LiteralPath $changelistPath -Force -ErrorAction SilentlyContinue
    }
    $first = @($changelist -split "`r?`n" | Where-Object { $_.Trim() })[0].Trim()
    if ($first -notmatch "^(\d+\.\d+\.\d+\.\d+)(?:\s|$)") {
        throw "Firmware changelist first line does not contain a version"
    }
    $latestVersion = $Matches[1]
    $latest = Parse-FirmwareVersion $latestVersion
    $comparison = Compare-FirmwareVersion $current $latest
    $relevantNotes = ""
    if ($comparison -lt 0) {
        $collected = [Collections.Generic.List[string]]::new()
        foreach ($line in ($changelist -split "`r?`n")) {
            if ($line -match ("^" + [regex]::Escape($CurrentVersion) + "(?:\s|$)")) { break }
            $collected.Add($line)
        }
        $relevantNotes = ($collected -join "`n").Trim()
    }
    $state = if ($comparison -lt 0) { "update_available" } `
        elseif ($comparison -eq 0) { "up_to_date" } else { "local_newer" }
    $result = [ordered]@{
        status = "ok"
        action = "device_update_check"
        cid = $Cid
        current_version = $CurrentVersion
        latest_version = $latestVersion
        update_available = ($comparison -lt 0)
        state = $state
        changelist_url = $changelistUrl
        release_notes = $relevantNotes
    }
} else {
    if ($InfoHex -notmatch "^[0-9A-Fa-f]{216}$") {
        throw "Firmware info payload must contain 216 hexadecimal characters"
    }
    if (-not $FirmwarePath) { throw "FirmwarePath is required" }
    $url = "${CgiUrl}?info=$InfoHex"
    Invoke-WebRequest -Uri $url -OutFile $FirmwarePath
    $file = Get-Item -LiteralPath $FirmwarePath
    if ($file.Length -lt 1024) { throw "Downloaded firmware is unexpectedly small" }
    $stream = [IO.File]::OpenRead($file.FullName)
    try {
        $head = New-Object byte[] ([Math]::Min(16, $file.Length))
        [void]$stream.Read($head, 0, $head.Length)
    } finally {
        $stream.Dispose()
    }
    $headText = [Text.Encoding]::ASCII.GetString($head)
    if ($headText -match "^\s*<(!DOCTYPE|html)") { throw "Firmware server returned HTML" }
    $result = [ordered]@{
        status = "ok"
        action = "device_firmware_download"
        bytes = $file.Length
        sha256 = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
    }
}

[IO.File]::WriteAllText([IO.Path]::GetFullPath($ResultPath),
    ($result | ConvertTo-Json -Compress) + "`n", [Text.UTF8Encoding]::new($false))
