#Requires -Version 7.4

function Invoke-vSEALCommandAction {
    <#
    .SYNOPSIS
        Executes a shell command with timeout enforcement.

    .DESCRIPTION
        Runs a command line with configurable timeout, captures output and exit codes,
        and returns a consistent result structure. Supports rollback command storage
        and recognizes Windows installer exit codes (3010 = reboot required).

    .PARAMETER Command
        The command line to execute.

    .PARAMETER Description
        Human-readable description for logging. Defaults to the command if not provided.

    .PARAMETER Timeout
        Timeout in seconds. Default: 120. Range: 1-600.

    .PARAMETER SuccessExitCodes
        Array of exit codes considered successful. Default: @(0, 3010).

    .PARAMETER RollbackCommand
        Optional command to run during rollback phase. Stored in Baseline for later use.

    .OUTPUTS
        [psobject] Result object with Action, Target, Status, Message, Baseline, Duration, ExitCode, RebootRequired.

    .EXAMPLE
        Invoke-vSEALCommandAction -Command 'reg.exe add "HKLM\SOFTWARE\Test" /v Value /t REG_SZ /d Data /f'
        Runs a registry command.

    .EXAMPLE
        Invoke-vSEALCommandAction -Command 'msiexec /i package.msi /qn' -Timeout 300 -Description 'Install MSI package'
        Runs an MSI installation with 5-minute timeout.
    #>
    [CmdletBinding()]
    [OutputType([psobject])]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$Command,

        [Parameter(Position = 1)]
        [string]$Description,

        [Parameter()]
        [ValidateRange(1, 600)]
        [int]$Timeout = 120,

        [Parameter()]
        [int[]]$SuccessExitCodes = @(0, 3010),

        [Parameter()]
        [string]$RollbackCommand
    )

    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()

    # Use Description or Command as target
    $target = if ($Description) { $Description } else { $Command }

    Write-vSEALLog -Message "Executing command: $Command (timeout: ${Timeout}s)" -Level 'VERBOSE'

    # Parse command into executable and arguments
    $parts = $Command -split ' ', 2
    $executable = $parts[0]
    $arguments = if ($parts.Length -gt 1) { $parts[1] } else { $null }

    # Create temp files for output capture
    $stdoutFile = [System.IO.Path]::GetTempFileName()
    $stderrFile = [System.IO.Path]::GetTempFileName()

    $process = $null
    $exitCode = $null
    $stdout = $null
    $stderr = $null
    $status = 'Success'
    $message = $null

    try {
        # Build Start-Process parameters
        $startParams = @{
            FilePath               = $executable
            NoNewWindow            = $true
            PassThru               = $true
            RedirectStandardOutput = $stdoutFile
            RedirectStandardError  = $stderrFile
        }

        if ($arguments) {
            $startParams['ArgumentList'] = $arguments
        }

        # Start the process
        $process = Start-Process @startParams

        # Wait with timeout
        $timeoutMs = $Timeout * 1000
        $completed = $process.WaitForExit($timeoutMs)

        if (-not $completed) {
            # Timeout - kill process
            Write-vSEALLog -Message "Command timed out after ${Timeout}s, killing process" -Level 'WARNING'
            try {
                $process.Kill()
                $process.WaitForExit(5000)  # Give 5s for graceful termination
            }
            catch {
                Write-vSEALLog -Message "Failed to kill process: $($_.Exception.Message)" -Level 'WARNING'
            }

            $stopwatch.Stop()
            $stdout = Get-Content -Path $stdoutFile -Raw -ErrorAction SilentlyContinue
            $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue

            return [PSCustomObject]@{
                Action         = 'command.run'
                Target         = $target
                Status         = 'Timeout'
                Message        = "Command timed out after ${Timeout}s"
                Baseline       = @{
                    RollbackCommand = $RollbackCommand
                    Output          = $stdout
                }
                Duration       = [int]$stopwatch.ElapsedMilliseconds
                ExitCode       = $null
                RebootRequired = $false
            }
        }

        # Process completed - get exit code and output
        $exitCode = $process.ExitCode
        $stdout = Get-Content -Path $stdoutFile -Raw -ErrorAction SilentlyContinue
        $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue

        Write-vSEALLog -Message "Command completed with exit code: $exitCode" -Level 'VERBOSE'

        # Check if exit code indicates success
        if ($exitCode -notin $SuccessExitCodes) {
            $status = 'Failed'
            $message = if ($stderr) {
                "Exit code $exitCode`: $($stderr.Trim())"
            }
            else {
                "Exit code $exitCode"
            }
            Write-vSEALLog -Message "Command failed: $message" -Level 'ERROR'
        }
    }
    catch {
        $stopwatch.Stop()
        Write-vSEALLog -Message "Failed to execute command: $($_.Exception.Message)" -Level 'ERROR'
        return [PSCustomObject]@{
            Action         = 'command.run'
            Target         = $target
            Status         = 'Failed'
            Message        = "Failed to execute: $($_.Exception.Message)"
            Baseline       = @{
                RollbackCommand = $RollbackCommand
                Output          = $null
            }
            Duration       = [int]$stopwatch.ElapsedMilliseconds
            ExitCode       = $null
            RebootRequired = $false
        }
    }
    finally {
        # Clean up temp files
        Remove-Item -Path $stdoutFile, $stderrFile -Force -ErrorAction SilentlyContinue
    }

    $stopwatch.Stop()

    # Check for reboot required (exit code 3010)
    $rebootRequired = $exitCode -eq 3010

    if ($rebootRequired) {
        Write-vSEALLog -Message "Exit code 3010: Reboot required" -Level 'INFO'
    }

    return [PSCustomObject]@{
        Action         = 'command.run'
        Target         = $target
        Status         = $status
        Message        = $message
        Baseline       = @{
            RollbackCommand = $RollbackCommand
            Output          = $stdout
        }
        Duration       = [int]$stopwatch.ElapsedMilliseconds
        ExitCode       = $exitCode
        RebootRequired = $rebootRequired
    }
}
