param (
    [switch]$Elevated
)

# Stop on first error
$ErrorActionPreference = "Stop"

#region Utility Functions

# Function to check if running as admin
function Test-Admin {
    return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

# Function to check if a command exists
function Test-CommandExists {
    param (
        [Parameter(Mandatory=$true)]
        [string]$Command
    )
    return (Get-Command $Command -ErrorAction SilentlyContinue)
}

# Function to find beszel-agent in common installation locations
function Find-BeszelAgent {
    # First check if it's in PATH
    $agentCmd = Get-Command "beszel-agent" -ErrorAction SilentlyContinue
    if ($agentCmd) {
        return $agentCmd.Source
    }
    
    # Common installation paths to check
    $commonPaths = @(
        "$env:USERPROFILE\scoop\apps\beszel-agent\current\beszel-agent.exe",
        "$env:ProgramData\scoop\apps\beszel-agent\current\beszel-agent.exe",
        "$env:LOCALAPPDATA\Microsoft\WinGet\Packages\henrygd.beszel-agent*\beszel-agent.exe",
        "$env:ProgramFiles\WinGet\Packages\henrygd.beszel-agent*\beszel-agent.exe",
        "${env:ProgramFiles(x86)}\WinGet\Packages\henrygd.beszel-agent*\beszel-agent.exe",
        "$env:ProgramFiles\beszel-agent\beszel-agent.exe",
        "$env:ProgramFiles(x86)\beszel-agent\beszel-agent.exe",
        "$env:SystemDrive\Users\*\scoop\apps\beszel-agent\current\beszel-agent.exe"
    )
    
    foreach ($path in $commonPaths) {
        # Handle wildcard paths
        if ($path.Contains("*")) {
            $foundPaths = Get-ChildItem -Path $path -ErrorAction SilentlyContinue
            if ($foundPaths) {
                return $foundPaths[0].FullName
            }
        } else {
            if (Test-Path $path) {
                return $path
            }
        }
    }
    
    return $null
}

# Function to find NSSM in common installation locations
function Find-NSSM {
    # First check if it's in PATH
    $nssmCmd = Get-Command "nssm" -ErrorAction SilentlyContinue
    if ($nssmCmd) {
        return $nssmCmd.Source
    }
    
    # Common installation paths to check
    $commonPaths = @(
        "$env:USERPROFILE\scoop\apps\nssm\current\nssm.exe",
        "$env:ProgramData\scoop\apps\nssm\current\nssm.exe",
        "$env:LOCALAPPDATA\Microsoft\WinGet\Packages\NSSM.NSSM*\nssm.exe",
        "$env:ProgramFiles\WinGet\Packages\NSSM.NSSM*\nssm.exe",
        "${env:ProgramFiles(x86)}\WinGet\Packages\NSSM.NSSM*\nssm.exe",
        "$env:SystemDrive\Users\*\scoop\apps\nssm\current\nssm.exe"
    )
    
    foreach ($path in $commonPaths) {
        # Handle wildcard paths
        if ($path.Contains("*")) {
            $foundPaths = Get-ChildItem -Path $path -ErrorAction SilentlyContinue
            if ($foundPaths) {
                return $foundPaths[0].FullName
            }
        } else {
            if (Test-Path $path) {
                return $path
            }
        }
    }
    
    return $null
}

#endregion

#region Upgrade Functions

# Function to upgrade beszel-agent with Scoop
function Upgrade-BeszelAgentWithScoop {
    Write-Host "Upgrading beszel-agent with Scoop..."
    scoop update beszel-agent
    
    if (-not (Test-CommandExists "beszel-agent")) {
        throw "Failed to upgrade beszel-agent with Scoop"
    }
    
    return $(Join-Path -Path $(scoop prefix beszel-agent) -ChildPath "beszel-agent.exe")
}

# Function to upgrade beszel-agent with WinGet
function Upgrade-BeszelAgentWithWinGet {
    Write-Host "Upgrading beszel-agent with WinGet..."
    
    # Temporarily change ErrorActionPreference to allow WinGet to complete and show output
    $originalErrorActionPreference = $ErrorActionPreference
    $ErrorActionPreference = "Continue"
    
    # Use call operator (&) and capture exit code properly
    & winget upgrade --exact --id henrygd.beszel-agent --accept-source-agreements --accept-package-agreements | Out-Null
    $wingetExitCode = $LASTEXITCODE
    
    # Restore original ErrorActionPreference
    $ErrorActionPreference = $originalErrorActionPreference
    
    # WinGet exit codes:
    # 0 = Success
    # -1978335212 (0x8A150014) = No applicable upgrade found (package is up to date)
    # -1978335189 (0x8A15002B) = Another "no upgrade needed" variant
    # Other codes indicate actual errors
    if ($wingetExitCode -eq -1978335212 -or $wingetExitCode -eq -1978335189) {
        Write-Host "Package is already up to date." -ForegroundColor Green
    } elseif ($wingetExitCode -ne 0)  {
        Write-Host "WinGet exit code: $wingetExitCode" -ForegroundColor Yellow
    }
    
    # Refresh PATH environment variable to make beszel-agent available in current session
    $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
    
    # Find the path to the beszel-agent executable
    $agentPath = (Get-Command beszel-agent -ErrorAction SilentlyContinue).Source
    
    if (-not $agentPath) {
        # Try to find it using our search function
        $agentPath = Find-BeszelAgent
        if (-not $agentPath) {
            throw "Could not find beszel-agent executable path after upgrade"
        }
    }
    
    return $agentPath
}

# Function to get current service configuration
function Get-ServiceConfiguration {
    param (
        [string]$NSSMPath = ""
    )
    
    # Determine the NSSM executable to use
    $nssmCommand = "nssm"
    if ($NSSMPath -and (Test-Path $NSSMPath)) {
        $nssmCommand = $NSSMPath
    } elseif (-not (Test-CommandExists "nssm")) {
        throw "NSSM is not available in PATH and no valid NSSMPath was provided"
    }
    
    # Check if service exists
    $existingService = Get-Service -Name "beszel-agent" -ErrorAction SilentlyContinue
    if (-not $existingService) {
        throw "beszel-agent service does not exist. Please run the installation script first."
    }
    
    # Get current service configuration
    $config = @{}
    
    try {
        # Get current application path
        $currentPath = & $nssmCommand get beszel-agent Application
        if ($LASTEXITCODE -eq 0) {
            $config.CurrentPath = $currentPath.Trim()
        }
        
        # Get environment variables
        $envVars = & $nssmCommand get beszel-agent AppEnvironmentExtra
        if ($LASTEXITCODE -eq 0 -and $envVars) {
            $config.EnvironmentVars = $envVars
        }
        
        Write-Host "Current service configuration retrieved successfully."
        Write-Host "Current agent path: $($config.CurrentPath)"
        
        return $config
    }
    catch {
        throw "Failed to retrieve current service configuration: $($_.Exception.Message)"
    }
}

# Function to update service path
function Update-ServicePath {
    param (
        [Parameter(Mandatory=$true)]
        [string]$NewAgentPath,
        [string]$NSSMPath = ""
    )
    
    Write-Host "Updating beszel-agent service path..."
    
    # Determine the NSSM executable to use
    $nssmCommand = "nssm"
    if ($NSSMPath -and (Test-Path $NSSMPath)) {
        $nssmCommand = $NSSMPath
        Write-Host "Using NSSM from: $NSSMPath"
    } elseif (-not (Test-CommandExists "nssm")) {
        throw "NSSM is not available in PATH and no valid NSSMPath was provided"
    }
    
    # Update the application path
    & $nssmCommand set beszel-agent Application $NewAgentPath
    if ($LASTEXITCODE -ne 0) {
        throw "Failed to update beszel-agent service path"
    }
    
    Write-Host "Service path updated to: $NewAgentPath"
    
    # Start the service
    Start-BeszelAgentService -NSSMPath $nssmCommand
}

# Function to start and monitor the service
function Start-BeszelAgentService {
    param (
        [string]$NSSMPath = ""
    )
    
    Write-Host "Starting beszel-agent service..."
    
    # Determine the NSSM executable to use
    $nssmCommand = "nssm"
    if ($NSSMPath -and (Test-Path $NSSMPath)) {
        $nssmCommand = $NSSMPath
    } elseif (-not (Test-CommandExists "nssm")) {
        throw "NSSM is not available in PATH and no valid NSSMPath was provided"
    }
    
    & $nssmCommand start beszel-agent
    $startResult = $LASTEXITCODE
    
    # Only enter the status check loop if the NSSM start command failed
    if ($startResult -ne 0) {
        Write-Host "NSSM start command returned error code: $startResult" -ForegroundColor Yellow
        Write-Host "This could be due to 'SERVICE_START_PENDING' state. Checking service status..."
        
        # Allow up to 10 seconds for the service to start, checking every second
        $maxWaitTime = 10 # seconds
        $elapsedTime = 0
        $serviceStarted = $false
        
        while (-not $serviceStarted -and $elapsedTime -lt $maxWaitTime) {
            Start-Sleep -Seconds 1
            $elapsedTime += 1

            $serviceStatus = & $nssmCommand status beszel-agent
            
            if ($serviceStatus -eq "SERVICE_RUNNING") {
                $serviceStarted = $true
                Write-Host "Success! The beszel-agent service is now running." -ForegroundColor Green
            }
            elseif ($serviceStatus -like "*PENDING*") {
                Write-Host "Service is still starting (status: $serviceStatus)... waiting" -ForegroundColor Yellow
            }
            else {
                Write-Host "Warning: The service status is '$serviceStatus' instead of 'SERVICE_RUNNING'." -ForegroundColor Yellow
                Write-Host "You may need to troubleshoot the service installation." -ForegroundColor Yellow
                break
            }
        }
        
        if (-not $serviceStarted) {
            Write-Host "Service did not reach running state." -ForegroundColor Yellow
            Write-Host "You can check status manually with 'nssm status beszel-agent'" -ForegroundColor Yellow
        }
    } else {
        # NSSM start command was successful
        Write-Host "Success! The beszel-agent service is running properly." -ForegroundColor Green
    }
}

#endregion

#region Main Script Execution

# Check if we're running as admin
$isAdmin = Test-Admin

try {
    Write-Host "Beszel Agent Upgrade Script" -ForegroundColor Cyan
    Write-Host "===========================" -ForegroundColor Cyan
    
    # First: Check if service exists (doesn't require admin)
    $existingService = Get-Service -Name "beszel-agent" -ErrorAction SilentlyContinue
    if (-not $existingService) {
        Write-Host "ERROR: beszel-agent service does not exist." -ForegroundColor Red
        Write-Host "Please run the installation script first before attempting to upgrade." -ForegroundColor Red
        exit 1
    }
    
    # Find current NSSM and agent paths
    $nssmPath = Find-NSSM
    if (-not $nssmPath -and (Test-CommandExists "nssm")) {
        $nssmPath = (Get-Command "nssm" -ErrorAction SilentlyContinue).Source
    }
    
    if (-not $nssmPath) {
        Write-Host "ERROR: NSSM not found. Cannot manage the service without NSSM." -ForegroundColor Red
        exit 1
    }
    
    # Get current service configuration (doesn't require admin)
    Write-Host "Retrieving current service configuration..."
    $currentConfig = Get-ServiceConfiguration -NSSMPath $nssmPath
    
    # Stop the service before upgrade
    Write-Host "Stopping beszel-agent service..."
    & $nssmPath stop beszel-agent
    if ($LASTEXITCODE -ne 0) {
        Write-Host "Warning: Failed to stop service, continuing anyway..." -ForegroundColor Yellow
    }
    
    # Upgrade the agent (doesn't require admin)
    Write-Host "Upgrading beszel-agent..."
    $newAgentPath = $null
    
    if (Test-CommandExists "scoop") {
        Write-Host "Using Scoop for upgrade..."
        $newAgentPath = Upgrade-BeszelAgentWithScoop
    }
    elseif (Test-CommandExists "winget") {
        Write-Host "Using WinGet for upgrade..."
        $newAgentPath = Upgrade-BeszelAgentWithWinGet
    }
    else {
        Write-Host "ERROR: Neither Scoop nor WinGet is available for upgrading." -ForegroundColor Red
        exit 1
    }
    
    if (-not $newAgentPath) {
        $newAgentPath = Find-BeszelAgent
        if (-not $newAgentPath) {
            throw "Could not find beszel-agent executable after upgrade."
        }
    }
    
    Write-Host "New agent path: $newAgentPath"
    
    # Check if the path has changed
    if ($currentConfig.CurrentPath -eq $newAgentPath) {
        Write-Host "Agent path has not changed. Restarting service..." -ForegroundColor Green
        Start-BeszelAgentService -NSSMPath $nssmPath
        Write-Host "Upgrade completed successfully!" -ForegroundColor Green
        exit 0
    }
    
    Write-Host "Agent path has changed from:" -ForegroundColor Yellow
    Write-Host "  Old: $($currentConfig.CurrentPath)" -ForegroundColor Yellow
    Write-Host "  New: $newAgentPath" -ForegroundColor Yellow
    Write-Host ""
    
    # If we need admin rights for service update and we don't have them, relaunch
    if (-not $isAdmin -and -not $Elevated) {
        Write-Host "Admin privileges required for service path update. Relaunching as admin..." -ForegroundColor Yellow
        
        # Prepare arguments for the elevated script
        $argumentList = @(
            "-ExecutionPolicy", "Bypass",
            "-File", "`"$PSCommandPath`"",
            "-Elevated"
        )
        
        # Relaunch the script with the -Elevated switch
        Start-Process powershell.exe -Verb RunAs -ArgumentList $argumentList
        exit
    }
    
    # Update service path (requires admin)
    if ($isAdmin -or $Elevated) {
        Update-ServicePath -NewAgentPath $newAgentPath -NSSMPath $nssmPath
        
        Write-Host ""
        Write-Host "Upgrade completed successfully!" -ForegroundColor Green
        Write-Host "The beszel-agent service has been updated to use the new executable path." -ForegroundColor Green
        
        # Pause to see results if this is an elevated window
        if ($Elevated) {
            Write-Host ""
            Write-Host "Press any key to exit..." -ForegroundColor Cyan
            $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
        }
    }
}
catch {
    Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "Upgrade failed. Please check the error message above." -ForegroundColor Red
    
    # Pause if this is likely a new window
    if ($Elevated -or (-not $isAdmin)) {
        Write-Host "Press any key to exit..." -ForegroundColor Red
        $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
    }
    exit 1
}

#endregion 