param(
  [Parameter(Mandatory = $true)]
  [string]$RuntimeSource,
  [string]$PackageDir = $PSScriptRoot,
  [string]$InstallRoot = (Join-Path $env:LOCALAPPDATA 'PLAI Canonical QBFT Node'),
  [switch]$SkipFirewall,
  [switch]$SkipStartupTask,
  [switch]$StartNow
)

$ErrorActionPreference = 'Stop'
$configPath = Join-Path $PackageDir 'node-config.json'
$manifestPath = Join-Path $PackageDir 'manifest.json'
foreach ($required in @($configPath, $manifestPath)) {
  if (-not (Test-Path -LiteralPath $required)) { throw "Required package file is missing: $required" }
}
$config = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
if (-not $config.finalized -or $config.mode -ne 'preserve-canonical-genesis') {
  throw 'Package is not finalized for canonical-genesis migration.'
}
foreach ($file in $manifest.files) {
  $candidate = Join-Path $PackageDir $file.name
  if (-not (Test-Path -LiteralPath $candidate)) { throw "Manifest file is missing: $($file.name)" }
  $actual = (Get-FileHash -LiteralPath $candidate -Algorithm SHA256).Hash.ToLowerInvariant()
  if ($actual -ne $file.sha256) { throw "Package hash mismatch: $($file.name)" }
}
$genesisPath = Join-Path $PackageDir 'genesis.json'
$genesisHash = (Get-FileHash -LiteralPath $genesisPath -Algorithm SHA256).Hash.ToLowerInvariant()
if ($genesisHash -ne ([string]$config.genesisSha256).ToLowerInvariant()) {
  throw 'Genesis hash does not match the canonical migration plan.'
}

$besuSource = Join-Path $RuntimeSource 'besu-26.6.1'
$jdkSource = Join-Path $RuntimeSource 'jdk-25.0.3+9'
if (-not (Test-Path -LiteralPath (Join-Path $besuSource 'lib'))) { throw 'Besu runtime is missing.' }
if (-not (Test-Path -LiteralPath (Join-Path (Join-Path $jdkSource 'bin') 'java.exe'))) { throw 'JDK runtime is missing.' }

New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
$runtimeRoot = Join-Path $InstallRoot 'runtime'
$besuDestination = Join-Path $runtimeRoot 'besu'
$jdkDestination = Join-Path $runtimeRoot 'jdk'
New-Item -ItemType Directory -Force -Path $runtimeRoot | Out-Null
if (-not (Test-Path -LiteralPath $besuDestination)) { Copy-Item -LiteralPath $besuSource -Destination $besuDestination -Recurse -Force }
else { Copy-Item -Path (Join-Path $besuSource '*') -Destination $besuDestination -Recurse -Force }
if (-not (Test-Path -LiteralPath $jdkDestination)) { Copy-Item -LiteralPath $jdkSource -Destination $jdkDestination -Recurse -Force }
else { Copy-Item -Path (Join-Path $jdkSource '*') -Destination $jdkDestination -Recurse -Force }
foreach ($file in @('node-config.json', 'manifest.json', 'genesis.json', 'static-nodes.json', 'key.pub', 'start-node.ps1', 'preflight-node.ps1', 'approve-node-activation.ps1')) {
  Copy-Item -LiteralPath (Join-Path $PackageDir $file) -Destination (Join-Path $InstallRoot $file) -Force
}

if ($config.role -eq 'validator') {
  $secretDir = Join-Path $InstallRoot 'secrets'
  $secretPath = Join-Path $secretDir 'validator.key'
  New-Item -ItemType Directory -Force -Path $secretDir | Out-Null
  if (Test-Path -LiteralPath $secretPath) {
    $existingHash = (Get-FileHash -LiteralPath $secretPath -Algorithm SHA256).Hash
    $packageHash = (Get-FileHash -LiteralPath (Join-Path $PackageDir 'validator.key') -Algorithm SHA256).Hash
    if ($existingHash -ne $packageHash) { throw 'Refusing to replace a different validator private key.' }
  }
  Copy-Item -LiteralPath (Join-Path $PackageDir 'validator.key') -Destination $secretPath -Force
  $currentSid = [Security.Principal.WindowsIdentity]::GetCurrent().User
  $requiredSids = @(
    $currentSid,
    [Security.Principal.SecurityIdentifier]::new('S-1-5-18'),
    [Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
  )
  foreach ($target in @($secretDir, $secretPath)) {
    $acl = Get-Acl -LiteralPath $target
    $acl.SetAccessRuleProtection($true, $false)
    foreach ($existing in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($existing) }
    $inheritance = [Security.AccessControl.InheritanceFlags]::None
    if ($target -eq $secretDir) {
      $inheritance = [Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [Security.AccessControl.InheritanceFlags]::ObjectInherit
    }
    foreach ($sid in $requiredSids) {
      $rule = [Security.AccessControl.FileSystemAccessRule]::new(
        $sid,
        [Security.AccessControl.FileSystemRights]::FullControl,
        $inheritance,
        [Security.AccessControl.PropagationFlags]::None,
        [Security.AccessControl.AccessControlType]::Allow
      )
      [void]$acl.AddAccessRule($rule)
    }
    Set-Acl -LiteralPath $target -AclObject $acl
  }
}

if (-not $SkipFirewall) {
  $admin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
  if (-not $admin) { throw 'Administrator rights are required to install the restricted P2P firewall rule.' }
  $ruleName = "PLAI QBFT $($config.nodeName) P2P"
  Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue | Remove-NetFirewallRule
  New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Allow -Protocol TCP -LocalPort $config.p2pPort -RemoteAddress @($config.allowedPeerHosts) -Profile Domain,Private | Out-Null
}

if (-not $SkipStartupTask) {
  $taskName = "PLAI QBFT $($config.nodeName)"
  $startScript = Join-Path $InstallRoot 'start-node.ps1'
  $argument = '-NoProfile -ExecutionPolicy RemoteSigned -WindowStyle Hidden -File "{0}"' -f $startScript
  $action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $argument
  $trigger = New-ScheduledTaskTrigger -AtStartup
  $principal = New-ScheduledTaskPrincipal -UserId ([Security.Principal.WindowsIdentity]::GetCurrent().Name) -LogonType S4U -RunLevel Highest
  $settings = New-ScheduledTaskSettingsSet -RestartCount 10 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero)
  Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
}

Write-Host "Installed $($config.role) package for $($config.nodeName)." -ForegroundColor Green
Write-Host 'The validator package will not start until explicit activation approval exists.'
if ($StartNow) {
  & (Join-Path $InstallRoot 'preflight-node.ps1') -InstallRoot $InstallRoot
  & (Join-Path $InstallRoot 'start-node.ps1') -InstallRoot $InstallRoot
}
