# Appmint hub agent — Windows installer (PowerShell).
#
# Installs Node via winget if missing, drops the agent under
# %ProgramFiles%\AppmintHub, registers as a Windows service (auto-start at
# boot, restart on crash) via node-windows, runs the setup wizard, starts
# the service.
#
# Usage (run as Administrator):
#   powershell -ExecutionPolicy Bypass -File scripts\install-windows.ps1
#
# Or via install.bat:
#   right-click install.bat -> Run as administrator
#
# Re-run upgrades the binary in place; config is preserved.

$ErrorActionPreference = 'Stop'
$NodeMajor = 20
# Pinned at publish time by deploy.mjs — see the note in install.sh. An
# installer fetched from the CDN only ever installs the build it shipped with.
# Override with:  $env:HUB_VERSION = '0.1.0'
$HubVersion = if ($env:HUB_VERSION) { $env:HUB_VERSION } else { 'dev' }
$ReleaseBaseUrl = if ($env:RELEASE_BASE_URL) { $env:RELEASE_BASE_URL } else { '__RELEASE_BASE_URL__' }
$InstallDir = "${env:ProgramFiles}\AppmintHub"
$ConfigDir = "${env:ProgramData}\AppmintHub"
$ServiceName = 'AppmintHub'

function Require-Admin {
  $current = [Security.Principal.WindowsIdentity]::GetCurrent()
  $principal = New-Object Security.Principal.WindowsPrincipal($current)
  if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Write-Error "Must run as Administrator. Right-click PowerShell -> Run as administrator, then re-run this script."
    exit 1
  }
}

function Ensure-Node {
  $nodeOk = $false
  if (Get-Command node -ErrorAction SilentlyContinue) {
    $v = (node -v).TrimStart('v').Split('.')[0]
    if ([int]$v -ge $NodeMajor) { $nodeOk = $true }
  }
  if (-not $nodeOk) {
    if (Get-Command winget -ErrorAction SilentlyContinue) {
      Write-Host "install: installing Node $NodeMajor via winget..."
      winget install -e --id "OpenJS.NodeJS.LTS" --accept-source-agreements --accept-package-agreements
      # winget doesn't refresh PATH for the current session; pull it.
      $env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')
    } elseif (Get-Command choco -ErrorAction SilentlyContinue) {
      Write-Host "install: installing Node $NodeMajor via chocolatey..."
      choco install nodejs-lts -y
      $env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')
    } else {
      Write-Error "Neither winget nor choco available. Install Node $NodeMajor+ manually from https://nodejs.org and re-run this script."
      exit 1
    }
  }
}

# Download the published tarball. It already contains a compiled dist/, so the
# client machine never needs TypeScript — only the two runtime dependencies.
function Install-AgentFromRelease {
  if ($ReleaseBaseUrl -eq '__RELEASE_BASE_URL__') {
    Write-Error "this installer was not published (no release URL baked in). Run it from a local clone."
    exit 1
  }

  $tarball = "hub-agent-$HubVersion.tar.gz"
  $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())
  New-Item -ItemType Directory -Path $tmp -Force | Out-Null
  try {
    Write-Host "install: downloading hub-agent $HubVersion..."
    Invoke-WebRequest -Uri "$ReleaseBaseUrl/$tarball" -OutFile "$tmp\$tarball" -UseBasicParsing

    # Verified for the same reason as the shell installers: this runs elevated
    # against a remote artifact, so a mismatch must stop the install.
    try {
      $meta = Invoke-RestMethod -Uri "$ReleaseBaseUrl/release.json" -UseBasicParsing
      if ($meta.sha256) {
        $have = (Get-FileHash "$tmp\$tarball" -Algorithm SHA256).Hash.ToLower()
        if ($have -ne $meta.sha256.ToLower()) {
          Write-Error "checksum mismatch for $tarball - refusing to install.`n  expected $($meta.sha256)`n  got      $have"
          exit 1
        }
      }
    } catch {
      Write-Host "install: no release.json alongside the tarball - skipping checksum."
    }

    # tar ships with Windows 10 1803+ and Server 2019+.
    cmd /c tar -xzf "$tmp\$tarball" -C "$tmp"
    if ($LASTEXITCODE -ne 0) { throw "failed to extract $tarball" }

    if (-not (Test-Path $InstallDir)) { New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null }
    robocopy "$tmp\hub-agent" $InstallDir /E /XD node_modules | Out-Null
    if ($LASTEXITCODE -ge 8) { throw "robocopy failed ($LASTEXITCODE)" }

    Push-Location $InstallDir
    try {
      # dist/ is prebuilt, so this pulls runtime deps only - no toolchain needed.
      cmd /c npm install --omit=dev --no-audit --no-fund
      if ($LASTEXITCODE -ne 0) { throw "npm install failed" }
    } finally {
      Pop-Location
    }
  } finally {
    Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
  }
}

function Install-Agent {
  Write-Host "install: copying agent to $InstallDir..."
  $src = (Resolve-Path "$PSScriptRoot\..").Path
  if (-not (Test-Path "$src\package.json")) {
    Install-AgentFromRelease
    return
  }
  if (-not (Test-Path $InstallDir)) { New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null }

  # Copy everything except node_modules/dist.
  robocopy $src $InstallDir /E /XD node_modules dist .git | Out-Null
  if ($LASTEXITCODE -ge 8) { Write-Error "robocopy failed ($LASTEXITCODE)"; exit 1 }

  Push-Location $InstallDir
  try {
    # Install full deps (TS + @types live in devDependencies and are needed
    # to compile). Build, then prune dev deps so production runtime is lean.
    Write-Host "install: installing dependencies..."
    cmd /c npm ci
    if ($LASTEXITCODE -ne 0) { throw "npm ci failed" }
    Write-Host "install: building..."
    cmd /c npm run build
    if ($LASTEXITCODE -ne 0) { throw "build failed" }
    Write-Host "install: pruning dev dependencies..."
    cmd /c npm prune --omit=dev
    # node-windows ships winsw — needed for service registration. Install
    # only at install time; not in package.json since the agent itself
    # doesn't need it at runtime.
    Write-Host "install: installing node-windows (one-time, for service registration)..."
    cmd /c npm install --no-save node-windows
    if ($LASTEXITCODE -ne 0) { throw "node-windows install failed" }
  } finally {
    Pop-Location
  }
}

function Run-Setup-If-Needed {
  if (-not (Test-Path "$ConfigDir\config.json")) {
    Write-Host "install: no existing config — running setup wizard"
    if (-not (Test-Path $ConfigDir)) { New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null }
    Push-Location $InstallDir
    try {
      cmd /c node dist\setup.js --out="$ConfigDir\config.json"
      if ($LASTEXITCODE -ne 0) { throw "setup wizard failed" }
    } finally {
      Pop-Location
    }
    return
  }

  # Existing config — show it and ask once. Default = keep + update software.
  Write-Host ""
  Write-Host "Existing config found at $ConfigDir\config.json:"
  try {
    $cfg = Get-Content "$ConfigDir\config.json" -Raw | ConvertFrom-Json
    Write-Host ("  hub:    " + $cfg.hubName)
    Write-Host ("  org:    " + $cfg.orgId)
    Write-Host ("  server: " + $cfg.serverUrl)
  } catch { }
  Write-Host ""
  Write-Host "  1) Keep existing config — just update the software  [default]"
  Write-Host "  2) Reconfigure — change credentials / server URL"
  Write-Host ""
  $choice = Read-Host "Choose 1 or 2 [1]"
  if (-not $choice) { $choice = '1' }
  if ($choice -eq '2') {
    Push-Location $InstallDir
    try {
      cmd /c node dist\setup.js --out="$ConfigDir\config.json"
    } finally { Pop-Location }
  } else {
    Write-Host "install: keeping existing config — software-only update"
  }
}

function Install-CliWrapper {
  # Add a hub-agent.cmd shim under %ProgramFiles%\AppmintHub so operators
  # can type `hub-agent status` from any elevated PowerShell.
  $wrapper = Join-Path $InstallDir 'hub-agent.cmd'
  $body = "@echo off`r`nnode `"$InstallDir\dist\cli.js`" %*`r`n"
  Set-Content -Path $wrapper -Value $body -Encoding ASCII
  # Add to system PATH if not present.
  $path = [Environment]::GetEnvironmentVariable('Path', 'Machine')
  if ($path -notlike "*$InstallDir*") {
    [Environment]::SetEnvironmentVariable('Path', "$path;$InstallDir", 'Machine')
    Write-Host "install: added $InstallDir to system PATH (open a new terminal to pick it up)"
  }
}

function Print-Summary {
  Write-Host ""
  Write-Host "=========================================================="
  Write-Host "  appmint-hub installed and RUNNING"
  Write-Host "=========================================================="
  Write-Host ""
  Write-Host "Service:    $ServiceName  (Windows service)"
  Write-Host "Status:     Get-Service $ServiceName"
  Write-Host "Auto-start: yes — will start automatically on every boot."
  Write-Host ""
  Write-Host "Files:"
  Write-Host "  install:  $InstallDir"
  Write-Host "  config:   $ConfigDir\config.json"
  Write-Host "  logs:     Event Viewer -> Applications and Services -> $ServiceName"
  Write-Host ""
  Write-Host "Commands (open a new terminal first to pick up PATH):"
  Write-Host "  hub-agent status        Connection state + config + recent log"
  Write-Host "  hub-agent logs          Show recent log lines"
  Write-Host "  hub-agent devices       List configured + USB-detected devices"
  Write-Host "  hub-agent setup         Reconfigure credentials / server URL"
  Write-Host "  hub-agent update        Update software (preserves config)"
  Write-Host "  hub-agent restart       Bounce the service"
  Write-Host "  hub-agent help          Full command list"
  Write-Host ""
  Write-Host "Verify it's connected:  hub-agent status"
  Write-Host "Or check the admin UI:  Operations -> Devices -> Hubs"
  Write-Host ""
}

function Install-Service {
  Write-Host "install: registering Windows service..."
  # If the service exists, stop + uninstall first so we re-register with
  # any updated paths.
  $existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
  if ($existing) {
    Write-Host "install: stopping existing service..."
    if ($existing.Status -ne 'Stopped') { Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue }
    Push-Location $InstallDir
    try {
      $env:APPMINT_HUB_INSTALL_DIR = $InstallDir
      $env:APPMINT_HUB_CONFIG = "$ConfigDir\config.json"
      cmd /c node scripts\win-service.js uninstall
    } finally { Pop-Location }
    Start-Sleep -Seconds 2
  }
  Push-Location $InstallDir
  try {
    $env:APPMINT_HUB_INSTALL_DIR = $InstallDir
    $env:APPMINT_HUB_CONFIG = "$ConfigDir\config.json"
    cmd /c node scripts\win-service.js install
    if ($LASTEXITCODE -ne 0) { throw "service install failed" }
  } finally {
    Pop-Location
  }
}

# --------- main ---------
Require-Admin
Ensure-Node
Install-Agent
Install-CliWrapper
Run-Setup-If-Needed
Install-Service
Print-Summary
