# Applies a downloaded Nexus POC package after Shell exits. Invoked by Nexus Shell auto-update.
param(
  [Parameter(Mandatory)][string]$ZipPath,
  [string]$InstallRoot = $PSScriptRoot,
  [int]$TargetBuild = 0
)

$ErrorActionPreference = "Stop"

function Write-ApplyLog([string]$Message) {
  $line = "{0} [APPLY] {1}" -f (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), $Message
  try {
    Add-Content -LiteralPath $applyLog -Value $line -Encoding UTF8
  } catch {}
}

function Stop-NexusUnderRoot([string]$Root) {
  $normalized = (Resolve-Path -LiteralPath $Root).Path.TrimEnd('\')
  $names = @("NexusShell", "NexusIdentity.Api")

  $pidFile = Join-Path $Root "logs\nexus-poc.pids.json"
  if (Test-Path -LiteralPath $pidFile) {
    try {
      $info = Get-Content $pidFile -Raw | ConvertFrom-Json
      foreach ($name in @("shellPid", "identityPid")) {
        $pidVal = [int]$info.$name
        if ($pidVal -gt 0) {
          Stop-Process -Id $pidVal -Force -ErrorAction SilentlyContinue
          Write-ApplyLog "Stopped PID $pidVal from pid file"
        }
      }
    } catch {}
    Remove-Item -LiteralPath $pidFile -Force -ErrorAction SilentlyContinue
  }

  foreach ($procName in $names) {
    Get-Process -Name $procName -ErrorAction SilentlyContinue | ForEach-Object {
      try {
        $path = $_.Path
        if ($path -and $path.StartsWith($normalized, [StringComparison]::OrdinalIgnoreCase)) {
          Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
          Write-ApplyLog "Stopped $procName PID $($_.Id)"
        }
      } catch {}
    }
  }
}

function Test-FileUnlocked([string]$Path) {
  if (-not (Test-Path -LiteralPath $Path)) { return $true }
  try {
    $fs = [System.IO.File]::Open($Path, 'Open', 'ReadWrite', 'None')
    $fs.Dispose()
    return $true
  } catch {
    return $false
  }
}

function Backup-LiveTree([string]$Root) {
  $prevDir = Join-Path $Root "versions\_previous"
  if (Test-Path -LiteralPath $prevDir) {
    Remove-Item -LiteralPath $prevDir -Recurse -Force
  }
  New-Item -ItemType Directory -Path $prevDir -Force | Out-Null
  foreach ($folder in @("shell", "identity")) {
    $src = Join-Path $Root $folder
    if (Test-Path -LiteralPath $src) {
      Copy-Item -LiteralPath $src -Destination (Join-Path $prevDir $folder) -Recurse -Force
      Write-ApplyLog "Backed up $folder to versions\_previous"
    }
  }
  return $prevDir
}

function Restore-PreviousTree([string]$Root) {
  $prevDir = Join-Path $Root "versions\_previous"
  if (-not (Test-Path -LiteralPath $prevDir)) {
    Write-ApplyLog "Rollback skipped: no versions\_previous backup"
    return $false
  }
  foreach ($folder in @("shell", "identity")) {
    $src = Join-Path $prevDir $folder
    $dst = Join-Path $Root $folder
    if (-not (Test-Path -LiteralPath $src)) { continue }
    if (Test-Path -LiteralPath $dst) {
      Remove-Item -LiteralPath $dst -Recurse -Force
    }
    Copy-Item -LiteralPath $src -Destination $dst -Recurse -Force
    Write-ApplyLog "Rollback restored $folder from versions\_previous"
  }
  return $true
}

function Expand-WithRetry([string]$Zip, [string]$Destination, [int]$MaxAttempts = 5) {
  for ($i = 1; $i -le $MaxAttempts; $i++) {
    try {
      if (Test-Path -LiteralPath $Destination) {
        # In-place overwrite: only shell/identity/logs/scripts — preserve data/
        Expand-Archive -LiteralPath $Zip -DestinationPath $Destination -Force
      } else {
        Expand-Archive -LiteralPath $Zip -DestinationPath $Destination -Force
      }
      return
    } catch {
      Write-ApplyLog "Expand attempt $i failed: $($_.Exception.Message)"
      Stop-NexusUnderRoot -Root $InstallRoot
      Start-Sleep -Seconds ([Math]::Min(3 * $i, 15))
      if ($i -eq $MaxAttempts) { throw }
    }
  }
}

if (-not (Test-Path -LiteralPath $ZipPath)) {
  throw "Update package missing: $ZipPath"
}

$InstallRoot = (Resolve-Path -LiteralPath $InstallRoot).Path
$logsDir = Join-Path $InstallRoot "logs"
$dataDir = Join-Path $InstallRoot "data\identity"
$applyLog = Join-Path $logsDir "apply-update.log"
$resultFile = Join-Path $logsDir "apply-result.json"
New-Item -ItemType Directory -Path $logsDir -Force | Out-Null
New-Item -ItemType Directory -Path $dataDir -Force | Out-Null

Write-ApplyLog "Apply started: zip=$ZipPath build=$TargetBuild root=$InstallRoot"

try {
  Stop-NexusUnderRoot -Root $InstallRoot
  Start-Sleep -Seconds 2
  Stop-NexusUnderRoot -Root $InstallRoot

  $lockTargets = @(
    (Join-Path $InstallRoot "identity\e_sqlite3.dll"),
    (Join-Path $InstallRoot "identity\NexusIdentity.Api.exe"),
    (Join-Path $InstallRoot "shell\NexusShell.exe")
  )
  foreach ($target in $lockTargets) {
    if (-not (Test-FileUnlocked -Path $target)) {
      throw "Locked file blocks update: $target"
    }
  }

  # Minimal versioned staging: extract to versions\{build}\ then mirror shell/identity into live tree.
  $versionDir = if ($TargetBuild -gt 0) {
    Join-Path $InstallRoot "versions\$TargetBuild"
  } else {
    Join-Path $InstallRoot "versions\pending"
  }
  if (Test-Path -LiteralPath $versionDir) {
    Remove-Item -LiteralPath $versionDir -Recurse -Force
  }
  New-Item -ItemType Directory -Path $versionDir -Force | Out-Null

  Expand-WithRetry -Zip $ZipPath -Destination $versionDir

  Backup-LiveTree -Root $InstallRoot | Out-Null

  foreach ($folder in @("shell", "identity")) {
    $src = Join-Path $versionDir $folder
    $dst = Join-Path $InstallRoot $folder
    if (-not (Test-Path -LiteralPath $src)) { throw "Package missing $folder after extract." }
    if (Test-Path -LiteralPath $dst) {
      Remove-Item -LiteralPath $dst -Recurse -Force
    }
    Copy-Item -LiteralPath $src -Destination $dst -Recurse -Force
  }

  foreach ($script in @(
    "apply-pending-update.ps1", "STOP-NEXUS.ps1", "START-NEXUS-SAFE-MODE.ps1",
    "README-FIRST.txt", "RECOVERY-INSTRUCTIONS.txt", "VERIFY-NEXUS-POC.ps1"
  )) {
    $srcScript = Join-Path $versionDir $script
    if (Test-Path -LiteralPath $srcScript) {
      Copy-Item -LiteralPath $srcScript -Destination (Join-Path $InstallRoot $script) -Force
    }
  }

  if ($TargetBuild -gt 0) {
    Set-Content -LiteralPath (Join-Path $InstallRoot "current-build.txt") -Value $TargetBuild -Encoding UTF8
  }

  $identityDir = Join-Path $InstallRoot "identity"
  $shellDir = Join-Path $InstallRoot "shell"
  $identityExe = Join-Path $identityDir "NexusIdentity.Api.exe"
  $shellExe = Join-Path $shellDir "NexusShell.exe"

  if (-not (Test-Path -LiteralPath $identityExe)) { throw "Identity missing after update." }
  if (-not (Test-Path -LiteralPath $shellExe)) { throw "Shell missing after update." }

  $dbPath = Join-Path $dataDir "nexus-identity.db"
  $dbConnection = "Data Source=$dbPath"

  $prevDbEnv = $env:NEXUS_DB_CONNECTION
  $env:NEXUS_DB_CONNECTION = $dbConnection
  try {
    Start-Process -FilePath $identityExe -ArgumentList "--urls http://localhost:5050" `
      -WorkingDirectory $identityDir -WindowStyle Hidden | Out-Null
  } finally {
    if ($null -eq $prevDbEnv) {
      Remove-Item -Path Env:NEXUS_DB_CONNECTION -ErrorAction SilentlyContinue
    } else {
      $env:NEXUS_DB_CONNECTION = $prevDbEnv
    }
  }

  Start-Sleep -Seconds 4
  Start-Process -FilePath $shellExe -ArgumentList "--safe-poc" -WorkingDirectory $shellDir | Out-Null

  Remove-Item -LiteralPath $ZipPath -Force -ErrorAction SilentlyContinue

  @{
    status = "completed"
    build = $TargetBuild
    completedUtc = (Get-Date).ToUniversalTime().ToString("o")
  } | ConvertTo-Json | Set-Content -LiteralPath $resultFile -Encoding UTF8

  # Clear pending job marker for Shell recovery
  $pendingJob = Join-Path $env:LOCALAPPDATA "NinjaNexus\update\pending-job.json"
  if (Test-Path -LiteralPath $pendingJob) {
    Remove-Item -LiteralPath $pendingJob -Force -ErrorAction SilentlyContinue
  }

  Write-ApplyLog "Apply completed successfully"
} catch {
  Write-ApplyLog "Apply failed: $($_.Exception.Message)"
  $rolledBack = Restore-PreviousTree -Root $InstallRoot
  @{
    status = "failed"
    build = $TargetBuild
    error = $_.Exception.Message
    rollbackAttempted = $true
    rollbackSucceeded = $rolledBack
    failedUtc = (Get-Date).ToUniversalTime().ToString("o")
  } | ConvertTo-Json | Set-Content -LiteralPath $resultFile -Encoding UTF8
  throw
}
