#Requires -Version 7.4

function Test-vSEALCondition {
    <#
    .SYNOPSIS
        Evaluates a single condition against system state.

    .DESCRIPTION
        Evaluates conditions for action group execution. Supports four condition types:
        - globals: Check OS/Platform values from the globals hashtable
        - registry: Check if HKLM registry keys exist
        - service: Check if Windows services exist
        - task: Check if scheduled tasks exist

        Returns $false on any error (never throws). HKCU registry paths are rejected
        for security reasons (VDI sealing operates at machine level).

    .PARAMETER Condition
        A hashtable defining the condition to evaluate. Must contain 'type' and
        type-specific fields:

        globals:  type, path, operator, value
        registry: type, key, operator (exists/not_exists)
        service:  type, name, operator (exists/not_exists)
        task:     type, name, operator (exists/not_exists)

    .PARAMETER Globals
        The globals hashtable containing OS and Platform information.
        Required for 'globals' condition type.

    .OUTPUTS
        [bool] $true if condition is met, $false otherwise.

    .EXAMPLE
        $condition = @{ type = 'globals'; path = 'OS.Build'; operator = '>='; value = 22000 }
        $globals = @{ OS = @{ Build = 22621 } }
        Test-vSEALCondition -Condition $condition -Globals $globals
        Returns $true because 22621 >= 22000

    .EXAMPLE
        $condition = @{ type = 'registry'; key = 'HKLM:\SOFTWARE\Policies\Microsoft'; operator = 'exists' }
        Test-vSEALCondition -Condition $condition
        Returns $true if the registry key exists

    .EXAMPLE
        $condition = @{ type = 'service'; name = 'Spooler'; operator = 'not_exists' }
        Test-vSEALCondition -Condition $condition
        Returns $false because Print Spooler service typically exists

    .EXAMPLE
        $condition = @{ type = 'task'; name = 'Microsoft\Windows\Defrag\ScheduledDefrag'; operator = 'exists' }
        Test-vSEALCondition -Condition $condition
        Returns $true if the defrag scheduled task exists
    #>
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)]
        [hashtable]$Condition,

        [Parameter()]
        [hashtable]$Globals
    )

    # Validate condition has required 'type' field
    if (-not $Condition.ContainsKey('type')) {
        Write-vSEALLog -Message "Condition missing required 'type' field" -Level 'VERBOSE'
        return $false
    }

    $conditionType = $Condition.type

    try {
        switch ($conditionType) {
            'globals' {
                return Invoke-GlobalsCondition -Condition $Condition -Globals $Globals
            }
            'registry' {
                return Invoke-RegistryCondition -Condition $Condition
            }
            'service' {
                return Invoke-ServiceCondition -Condition $Condition
            }
            'task' {
                return Invoke-TaskCondition -Condition $Condition
            }
            default {
                Write-vSEALLog -Message "Unknown condition type: $conditionType" -Level 'VERBOSE'
                return $false
            }
        }
    }
    catch {
        Write-vSEALLog -Message "Exception evaluating condition type '$conditionType': $($_.Exception.Message)" -Level 'VERBOSE'
        return $false
    }
}

function Invoke-GlobalsCondition {
    <#
    .SYNOPSIS
        Evaluates a globals condition against the globals hashtable.
    #>
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)]
        [hashtable]$Condition,

        [Parameter()]
        [hashtable]$Globals
    )

    # Validate required fields
    if (-not $Condition.ContainsKey('path')) {
        Write-vSEALLog -Message "Globals condition missing required 'path' field" -Level 'VERBOSE'
        return $false
    }
    if (-not $Condition.ContainsKey('operator')) {
        Write-vSEALLog -Message "Globals condition missing required 'operator' field" -Level 'VERBOSE'
        return $false
    }
    if (-not $Condition.ContainsKey('value')) {
        Write-vSEALLog -Message "Globals condition missing required 'value' field" -Level 'VERBOSE'
        return $false
    }

    if ($null -eq $Globals) {
        Write-vSEALLog -Message "Globals hashtable is null" -Level 'VERBOSE'
        return $false
    }

    $path = $Condition.path
    $operator = $Condition.operator
    $expectedValue = $Condition.value

    # Navigate the globals hashtable using dot-notation path
    $actualValue = Get-HashtableValueByPath -Hashtable $Globals -Path $path
    if ($null -eq $actualValue) {
        Write-vSEALLog -Message "Path '$path' not found in globals or value is null" -Level 'VERBOSE'
        return $false
    }

    # Apply operator comparison
    $result = Invoke-OperatorComparison -ActualValue $actualValue -Operator $operator -ExpectedValue $expectedValue
    Write-vSEALLog -Message "Globals condition: $path $operator $expectedValue = $result (actual: $actualValue)" -Level 'VERBOSE'
    return $result
}

function Get-HashtableValueByPath {
    <#
    .SYNOPSIS
        Navigates a nested hashtable using dot-notation path (case-insensitive).
    #>
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory)]
        [hashtable]$Hashtable,

        [Parameter(Mandatory)]
        [string]$Path
    )

    $segments = $Path -split '\.'
    $current = $Hashtable

    foreach ($segment in $segments) {
        if ($null -eq $current) {
            return $null
        }

        if ($current -is [hashtable]) {
            # Case-insensitive key lookup
            $key = $current.Keys | Where-Object { $_ -ieq $segment } | Select-Object -First 1
            if ($null -eq $key) {
                return $null
            }
            $current = $current[$key]
        }
        else {
            # Try property access for objects
            try {
                $current = $current.$segment
            }
            catch {
                return $null
            }
        }
    }

    return $current
}

function Invoke-OperatorComparison {
    <#
    .SYNOPSIS
        Applies an operator comparison between actual and expected values.
    #>
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)]
        [AllowNull()]
        $ActualValue,

        [Parameter(Mandatory)]
        [string]$Operator,

        [Parameter(Mandatory)]
        [AllowNull()]
        $ExpectedValue
    )

    switch ($Operator) {
        '=' { return $ActualValue -eq $ExpectedValue }
        '!=' { return $ActualValue -ne $ExpectedValue }
        '>' { return $ActualValue -gt $ExpectedValue }
        '>=' { return $ActualValue -ge $ExpectedValue }
        '<' { return $ActualValue -lt $ExpectedValue }
        '<=' { return $ActualValue -le $ExpectedValue }
        'in' { return $ActualValue -in $ExpectedValue }
        'not_in' { return $ActualValue -notin $ExpectedValue }
        'contains' { return $ExpectedValue -in $ActualValue }
        default {
            Write-vSEALLog -Message "Unknown operator: $Operator" -Level 'VERBOSE'
            return $false
        }
    }
}

function Invoke-RegistryCondition {
    <#
    .SYNOPSIS
        Evaluates a registry key existence condition.
    #>
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)]
        [hashtable]$Condition
    )

    # Validate required fields
    if (-not $Condition.ContainsKey('key')) {
        Write-vSEALLog -Message "Registry condition missing required 'key' field" -Level 'VERBOSE'
        return $false
    }
    if (-not $Condition.ContainsKey('operator')) {
        Write-vSEALLog -Message "Registry condition missing required 'operator' field" -Level 'VERBOSE'
        return $false
    }

    $key = $Condition.key
    $operator = $Condition.operator

    # Reject HKCU paths - VDI sealing operates at machine level only
    if ($key -match '^HKCU:' -or $key -match '^HKEY_CURRENT_USER') {
        Write-vSEALLog -Message "Registry condition rejected: HKCU paths not allowed for VDI sealing (key: $key)" -Level 'VERBOSE'
        return $false
    }

    # Check key existence
    $keyExists = Test-Path -LiteralPath $key -ErrorAction SilentlyContinue

    switch ($operator) {
        'exists' {
            Write-vSEALLog -Message "Registry condition: key '$key' exists = $keyExists" -Level 'VERBOSE'
            return $keyExists
        }
        'not_exists' {
            $result = -not $keyExists
            Write-vSEALLog -Message "Registry condition: key '$key' not_exists = $result" -Level 'VERBOSE'
            return $result
        }
        default {
            Write-vSEALLog -Message "Unknown registry operator: $operator (expected exists/not_exists)" -Level 'VERBOSE'
            return $false
        }
    }
}

function Invoke-ServiceCondition {
    <#
    .SYNOPSIS
        Evaluates a service existence condition.
    #>
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)]
        [hashtable]$Condition
    )

    # Validate required fields
    if (-not $Condition.ContainsKey('name')) {
        Write-vSEALLog -Message "Service condition missing required 'name' field" -Level 'VERBOSE'
        return $false
    }
    if (-not $Condition.ContainsKey('operator')) {
        Write-vSEALLog -Message "Service condition missing required 'operator' field" -Level 'VERBOSE'
        return $false
    }

    $serviceName = $Condition.name
    $operator = $Condition.operator

    try {
        $serviceExists = Test-vSEALServiceExists -Name $serviceName
    }
    catch {
        Write-vSEALLog -Message "Error checking service '$serviceName': $($_.Exception.Message)" -Level 'VERBOSE'
        return $false
    }

    switch ($operator) {
        'exists' {
            Write-vSEALLog -Message "Service condition: '$serviceName' exists = $serviceExists" -Level 'VERBOSE'
            return $serviceExists
        }
        'not_exists' {
            $result = -not $serviceExists
            Write-vSEALLog -Message "Service condition: '$serviceName' not_exists = $result" -Level 'VERBOSE'
            return $result
        }
        default {
            Write-vSEALLog -Message "Unknown service operator: $operator (expected exists/not_exists)" -Level 'VERBOSE'
            return $false
        }
    }
}

function Invoke-TaskCondition {
    <#
    .SYNOPSIS
        Evaluates a scheduled task existence condition.
    #>
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)]
        [hashtable]$Condition
    )

    # Validate required fields
    if (-not $Condition.ContainsKey('name')) {
        Write-vSEALLog -Message "Task condition missing required 'name' field" -Level 'VERBOSE'
        return $false
    }
    if (-not $Condition.ContainsKey('operator')) {
        Write-vSEALLog -Message "Task condition missing required 'operator' field" -Level 'VERBOSE'
        return $false
    }

    $taskName = $Condition.name
    $operator = $Condition.operator

    try {
        $taskExists = Test-vSEALTaskExists -Name $taskName
    }
    catch {
        Write-vSEALLog -Message "Error checking task '$taskName': $($_.Exception.Message)" -Level 'VERBOSE'
        return $false
    }

    switch ($operator) {
        'exists' {
            Write-vSEALLog -Message "Task condition: '$taskName' exists = $taskExists" -Level 'VERBOSE'
            return $taskExists
        }
        'not_exists' {
            $result = -not $taskExists
            Write-vSEALLog -Message "Task condition: '$taskName' not_exists = $result" -Level 'VERBOSE'
            return $result
        }
        default {
            Write-vSEALLog -Message "Unknown task operator: $operator (expected exists/not_exists)" -Level 'VERBOSE'
            return $false
        }
    }
}
