#Requires -Version 7.4

function Invoke-vSEALRegistryAction {
    <#
    .SYNOPSIS
        Executes a registry action with verification and baseline capture.

    .DESCRIPTION
        Executes registry modifications (Set, Delete, DeleteKey) on HKLM paths only,
        following the vARC collect-execute-verify-capture pattern. Captures the original
        state as baseline for rollback support.

        This is a vARC executor function that wraps the underlying registry functions
        (Get-vSEALRegistryKey, Set-vSEALRegistryKey, Remove-vSEALRegistryKey) with
        consistent result structure and verification.

    .PARAMETER Action
        The operation to perform:
        - Set: Create or update a registry value
        - Delete: Remove a specific registry value
        - DeleteKey: Remove an entire registry key

    .PARAMETER Key
        The registry path. Only HKLM (HKEY_LOCAL_MACHINE) paths are accepted.
        HKCU paths are rejected with an error directing users to policy frameworks.

    .PARAMETER Name
        The registry value name. Required for Set and Delete actions.
        Not used for DeleteKey action.

    .PARAMETER Data
        The value data to set. Required for Set action only.

    .PARAMETER Type
        The registry value type. Defaults to String.
        Valid values: String, DWord, QWord, Binary, ExpandString, MultiString

    .OUTPUTS
        [PSCustomObject] Result object with Action, Target, Status, Message, Baseline, Duration.

    .EXAMPLE
        Invoke-vSEALRegistryAction -Action Set -Key 'HKLM:\SOFTWARE\Policies\Test' -Name 'Setting' -Data 1 -Type DWord
        Sets a DWORD value and returns the result with baseline capture.

    .EXAMPLE
        Invoke-vSEALRegistryAction -Action Delete -Key 'HKLM:\SOFTWARE\Test' -Name 'OldValue'
        Deletes a specific registry value.

    .EXAMPLE
        Invoke-vSEALRegistryAction -Action DeleteKey -Key 'HKLM:\SOFTWARE\Test\OldKey'
        Removes an entire registry key.

    .NOTES
        Part of the vARC (vSEAL Action Rollback Core) infrastructure.
        HKCU/user registry is intentionally out of scope - use GPO/Intune for user settings.
    #>
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('Set', 'Delete', 'DeleteKey')]
        [string]$Action,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Key,

        [Parameter()]
        [string]$Name,

        [Parameter()]
        $Data,

        [Parameter()]
        [ValidateSet('String', 'DWord', 'QWord', 'Binary', 'ExpandString', 'MultiString')]
        [string]$Type = 'String'
    )

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

    # HKCU rejection check (EXEC-05)
    if ($Key -match '^HKCU:?\\' -or $Key -match '^HKEY_CURRENT_USER\\') {
        throw [System.ArgumentException]::new(
            'User-level registry is out of scope. Use policy frameworks (GPO, Intune, UEM) for user settings.'
        )
    }

    # Validate parameters based on action
    if ($Action -in @('Set', 'Delete') -and [string]::IsNullOrEmpty($Name)) {
        throw [System.ArgumentException]::new(
            "The -Name parameter is required for '$Action' action."
        )
    }

    if ($Action -eq 'Set' -and $null -eq $Data -and $Data -isnot [string]) {
        # Allow empty string but not null
        if ($null -eq $Data) {
            throw [System.ArgumentException]::new(
                "The -Data parameter is required for 'Set' action."
            )
        }
    }

    # Build target string for result
    $target = if ($Action -eq 'DeleteKey') {
        $Key
    } else {
        "$Key\$Name"
    }

    # Initialize result object
    $result = [PSCustomObject]@{
        Action   = "registry.$($Action.ToLower())"
        Target   = $target
        Status   = 'Failed'
        Message  = $null
        Baseline = $null
        Duration = 0
    }

    try {
        # COLLECT: Capture original state before modification (EXEC-06 step 1)
        $originalValue = $null
        $originalType = $null
        $keyExists = Test-Path -LiteralPath $Key -ErrorAction SilentlyContinue

        if ($keyExists) {
            if ($Action -eq 'DeleteKey') {
                # For DeleteKey, capture that the key existed
                $originalValue = $true
                $originalType = 'Key'
            } else {
                # Get the current value
                try {
                    $currentValue = Get-vSEALRegistryKey -LiteralPath $Key -Name $Name -ErrorAction SilentlyContinue
                    if ($null -ne $currentValue) {
                        $originalValue = $currentValue

                        # Try to get the type
                        try {
                            $item = Get-Item -LiteralPath $Key -ErrorAction SilentlyContinue
                            if ($item) {
                                $originalType = $item.GetValueKind($Name).ToString()
                            }
                        } catch {
                            $originalType = 'Unknown'
                        }
                    }
                } catch {
                    # Value doesn't exist, that's okay for Set action
                    $originalValue = $null
                    $originalType = $null
                }
            }
        }

        # Store baseline
        $result.Baseline = @{
            Data = $originalValue
            Type = $originalType
        }

        # EXECUTE: Perform the action (EXEC-06 step 2)
        switch ($Action) {
            'Set' {
                # Convert type string to RegistryValueKind
                $registryType = switch ($Type) {
                    'String' { [Microsoft.Win32.RegistryValueKind]::String }
                    'DWord' { [Microsoft.Win32.RegistryValueKind]::DWord }
                    'QWord' { [Microsoft.Win32.RegistryValueKind]::QWord }
                    'Binary' { [Microsoft.Win32.RegistryValueKind]::Binary }
                    'ExpandString' { [Microsoft.Win32.RegistryValueKind]::ExpandString }
                    'MultiString' { [Microsoft.Win32.RegistryValueKind]::MultiString }
                }

                Set-vSEALRegistryKey -LiteralPath $Key -Name $Name -Value $Data -Type $registryType -ErrorAction Stop
            }
            'Delete' {
                Remove-vSEALRegistryKey -LiteralPath $Key -Name $Name -ErrorAction Stop
            }
            'DeleteKey' {
                Remove-vSEALRegistryKey -LiteralPath $Key -Recurse -ErrorAction Stop
            }
        }

        # VERIFY: Read back and compare (EXEC-06 step 3)
        $verified = $false

        switch ($Action) {
            'Set' {
                $newValue = Get-vSEALRegistryKey -LiteralPath $Key -Name $Name -ErrorAction SilentlyContinue

                # Handle binary comparison specially
                # Note: PowerShell may return byte[] as Object[] when returned from functions
                if ($Type -eq 'Binary' -and $Data -is [byte[]] -and $null -ne $newValue -and $newValue -is [array]) {
                    if ($Data.Length -eq $newValue.Length) {
                        $verified = $true
                        for ($i = 0; $i -lt $Data.Length; $i++) {
                            if ([byte]$Data[$i] -ne [byte]$newValue[$i]) {
                                $verified = $false
                                break
                            }
                        }
                    }
                } elseif ($Type -eq 'MultiString' -and $Data -is [array] -and $null -ne $newValue -and $newValue -is [array]) {
                    # Compare string arrays
                    if ($Data.Length -eq $newValue.Length) {
                        $verified = $true
                        for ($i = 0; $i -lt $Data.Length; $i++) {
                            if ([string]$Data[$i] -ne [string]$newValue[$i]) {
                                $verified = $false
                                break
                            }
                        }
                    }
                } else {
                    # Standard comparison
                    $verified = ($newValue -eq $Data)
                }

                if (-not $verified) {
                    $result.Message = "Verification failed: expected '$Data', got '$newValue'"
                }
            }
            'Delete' {
                $newValue = Get-vSEALRegistryKey -LiteralPath $Key -Name $Name -ErrorAction SilentlyContinue
                $verified = ($null -eq $newValue)

                if (-not $verified) {
                    $result.Message = "Verification failed: value still exists after delete"
                }
            }
            'DeleteKey' {
                $keyStillExists = Test-Path -LiteralPath $Key -ErrorAction SilentlyContinue
                $verified = (-not $keyStillExists)

                if (-not $verified) {
                    $result.Message = "Verification failed: key still exists after delete"
                }
            }
        }

        if ($verified) {
            $result.Status = 'Success'
        }
    }
    catch {
        $result.Status = 'Failed'
        $result.Message = $_.Exception.Message
    }
    finally {
        $stopwatch.Stop()
        $result.Duration = [int]$stopwatch.ElapsedMilliseconds
    }

    return $result
}
