internal/Initialize-EasyPIMPolicies.ps1

# Initialize-EasyPIMPolicies function for EasyPIM.Orchestrator
# Simplified version focused on the orchestrator's needs

function Initialize-EasyPIMPolicies {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Config,

        [Parameter(Mandatory = $false)]
        [ValidateSet("All", "AzureRoles", "EntraRoles", "GroupRoles")]
        [string[]]$PolicyOperations = @("All")
    )

    Write-Verbose "Starting Initialize-EasyPIMPolicies (Orchestrator)"

    try {
        $processedConfig = @{}

        # Initialize policy templates if they exist
        $policyTemplates = @{}
        if ($Config.PSObject.Properties['PolicyTemplates'] -and $Config.PolicyTemplates) {
            foreach ($templateName in $Config.PolicyTemplates.PSObject.Properties.Name) {
                $policyTemplates[$templateName] = $Config.PolicyTemplates.$templateName
            }
            Write-Verbose "Found $($policyTemplates.Keys.Count) policy templates"
        }

        $processAzure  = ($PolicyOperations -contains 'All' -or $PolicyOperations -contains 'AzureRoles')
        $processEntra  = ($PolicyOperations -contains 'All' -or $PolicyOperations -contains 'EntraRoles')
        $processGroups = ($PolicyOperations -contains 'All' -or $PolicyOperations -contains 'GroupRoles')

        # New format sections - EntraRoles.Policies
        if ($processEntra -and $Config.PSObject.Properties['EntraRoles'] -and $Config.EntraRoles.PSObject.Properties['Policies'] -and $Config.EntraRoles.Policies) {
            Write-Verbose "Processing EntraRoles.Policies section"
            $processedConfig.EntraRolePolicies = @()
            foreach ($roleName in $Config.EntraRoles.Policies.PSObject.Properties.Name) {
                $policyContent = $Config.EntraRoles.Policies.$roleName
                if ($policyContent.PSObject.Properties['Template'] -and $policyContent.Template) {
                    $policyDefinition = [PSCustomObject]@{ RoleName = $roleName; PolicySource = 'template'; Template = $policyContent.Template }
                } else {
                    $policyDefinition = [PSCustomObject]@{ RoleName = $roleName; PolicySource = 'inline'; Policy = $policyContent }
                }
                $processedPolicy = Resolve-PolicyConfiguration -PolicyDefinition $policyDefinition -Templates $policyTemplates -PolicyType 'EntraRole'
                $processedConfig.EntraRolePolicies += $processedPolicy
            }
            Write-Verbose "Processed $($processedConfig.EntraRolePolicies.Count) Entra Role policies from EntraRoles.Policies"
        }

        # New format sections - AzureRoles.Policies
        if ($processAzure -and $Config.PSObject.Properties['AzureRoles'] -and $Config.AzureRoles.PSObject.Properties['Policies'] -and $Config.AzureRoles.Policies) {
            Write-Verbose "Processing AzureRoles.Policies section"
            $processedConfig.AzureRolePolicies = @()
            foreach ($roleName in $Config.AzureRoles.Policies.PSObject.Properties.Name) {
                $policyContent = $Config.AzureRoles.Policies.$roleName
                $scope = $null
                if ($policyContent.PSObject.Properties['Scope'] -and $policyContent.Scope) { $scope = $policyContent.Scope }
                if ($policyContent.PSObject.Properties['Template'] -and $policyContent.Template) {
                    $policyDefinition = [PSCustomObject]@{ RoleName = $roleName; Scope = $scope; PolicySource = 'template'; Template = $policyContent.Template }
                } else {
                    $policyOnly = $policyContent | Select-Object -Property * -ExcludeProperty Scope
                    $policyDefinition = [PSCustomObject]@{ RoleName = $roleName; Scope = $scope; PolicySource = 'inline'; Policy = $policyOnly }
                }
                $processedPolicy = Resolve-PolicyConfiguration -PolicyDefinition $policyDefinition -Templates $policyTemplates -PolicyType 'AzureRole'
                $processedConfig.AzureRolePolicies += $processedPolicy
            }
            Write-Verbose "Processed $($processedConfig.AzureRolePolicies.Count) Azure Role policies from AzureRoles.Policies"
        }

        # New format sections - GroupRoles.Policies
        if ($processGroups -and $Config.PSObject.Properties['GroupRoles'] -and $Config.GroupRoles.PSObject.Properties['Policies'] -and $Config.GroupRoles.Policies) {
            Write-Verbose "Processing GroupRoles.Policies section"
            $processedConfig.GroupPolicies = @()
            foreach ($groupKey in $Config.GroupRoles.Policies.PSObject.Properties.Name) {
                $groupNode = $Config.GroupRoles.Policies.$groupKey
                $isGuid = $false
                if ($groupKey -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') { $isGuid = $true }
                foreach ($roleProp in $groupNode.PSObject.Properties) {
                    $roleName = $roleProp.Name
                    if ($roleName -in @('Member','Owner')) {
                        $roleContent = $roleProp.Value
                        if ($roleContent.PSObject.Properties['Template'] -and $roleContent.Template) {
                            if ($isGuid) {
                                $policyDefinition = [PSCustomObject]@{ PolicySource = 'template'; Template = $roleContent.Template; RoleName = $roleName; GroupId = $groupKey }
                            } else {
                                $policyDefinition = [PSCustomObject]@{ PolicySource = 'template'; Template = $roleContent.Template; RoleName = $roleName; GroupName = $groupKey }
                            }
                        } else {
                            if ($isGuid) {
                                $policyDefinition = [PSCustomObject]@{ PolicySource = 'inline'; Policy = $roleContent; RoleName = $roleName; GroupId = $groupKey }
                            } else {
                                $policyDefinition = [PSCustomObject]@{ PolicySource = 'inline'; Policy = $roleContent; RoleName = $roleName; GroupName = $groupKey }
                            }
                        }
                        $processedPolicy = Resolve-PolicyConfiguration -PolicyDefinition $policyDefinition -Templates $policyTemplates -PolicyType 'Group'
                        $processedConfig.GroupPolicies += $processedPolicy
                    }
                }
            }
            Write-Verbose "Processed $($processedConfig.GroupPolicies.Count) Group policies from GroupRoles.Policies"
        }

        # Pass-through other sections
        $existingSections = @('AzureRoles', 'AzureRolesActive', 'EntraIDRoles', 'EntraIDRolesActive', 'GroupRoles', 'GroupRolesActive', 'ProtectedUsers', 'Assignments')
        foreach ($section in $existingSections) {
            if ($Config.PSObject.Properties[$section] -and $Config.$section) { $processedConfig[$section] = $Config.$section }
        }

        Write-Verbose "Initialize-EasyPIMPolicies completed successfully"
        return $processedConfig
    }
    catch {
        Write-Error "Failed to initialize PIM policies: $($_.Exception.Message)"
        throw
    }
}

function Resolve-PolicyConfiguration {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)] [PSCustomObject]$PolicyDefinition,
        [Parameter(Mandatory = $true)] [hashtable]$Templates,
        [Parameter(Mandatory = $true)] [ValidateSet('AzureRole','EntraRole','Group')] [string]$PolicyType
    )

    # Helper function to auto-configure permanent assignment flags based on duration settings
    function Set-AutoPermanentFlags {
        param([PSCustomObject]$Policy, [string]$RoleName)

        if (-not $Policy) { return }

        $autoConfigured = @()

        # Auto-configure AllowPermanentEligibility based on MaximumEligibilityDuration
        if ($Policy.PSObject.Properties['MaximumEligibilityDuration'] -and
            $Policy.MaximumEligibilityDuration -and
            $Policy.MaximumEligibilityDuration -ne '') {

            if (-not $Policy.PSObject.Properties['AllowPermanentEligibility']) {
                $Policy | Add-Member -NotePropertyName 'AllowPermanentEligibility' -NotePropertyValue $false
                $autoConfigured += "AllowPermanentEligibility=false (MaximumEligibilityDuration specified)"
            }
        }

        # Auto-configure AllowPermanentActiveAssignment based on MaximumActiveAssignmentDuration
        if ($Policy.PSObject.Properties['MaximumActiveAssignmentDuration'] -and
            $Policy.MaximumActiveAssignmentDuration -and
            $Policy.MaximumActiveAssignmentDuration -ne '') {

            if (-not $Policy.PSObject.Properties['AllowPermanentActiveAssignment']) {
                $Policy | Add-Member -NotePropertyName 'AllowPermanentActiveAssignment' -NotePropertyValue $false
                $autoConfigured += "AllowPermanentActiveAssignment=false (MaximumActiveAssignmentDuration specified)"
            }
        }

        # Log auto-configuration for transparency
        if ($autoConfigured.Count -gt 0) {
            Write-Verbose "[Auto-Config] ${RoleName}: $($autoConfigured -join ', ')"
        }
    }

    Write-Verbose "Resolving policy configuration for $PolicyType"
    $resolvedPolicy = @{}

    # Copy non-empty properties from PolicyDefinition first
    foreach ($property in $PolicyDefinition.PSObject.Properties) {
        # Only copy non-empty values to allow template defaults to fill in blanks
        if ($null -ne $property.Value -and $property.Value -ne '') {
            $resolvedPolicy[$property.Name] = $property.Value
        }
    }

    switch ($PolicyType) {
        'AzureRole' { if (-not $PolicyDefinition.RoleName) { throw 'AzureRole policy missing required property: RoleName' }
                      if (-not $PolicyDefinition.Scope) { throw 'AzureRole policy missing required property: Scope' } }
        'EntraRole' { if (-not $PolicyDefinition.RoleName) { throw 'EntraRole policy missing required property: RoleName' } }
        'Group'     { if (-not $PolicyDefinition.GroupId -and -not $PolicyDefinition.GroupName) { throw 'Group policy missing required property: GroupId or GroupName' }
                      if (-not $PolicyDefinition.RoleName) { throw 'Group policy missing required property: RoleName' } }
    }

    $policySource = $PolicyDefinition.policySource
    if (-not $policySource) { if ($PolicyDefinition.template) { $policySource = 'template' } else { throw 'Policy definition missing policySource property' } }

    switch ($policySource.ToLower()) {
        'inline'   { if (-not $PolicyDefinition.policy) { throw 'Inline policy source specified but policy property is missing' }
                     $resolvedPolicy.ResolvedPolicy = $PolicyDefinition.policy
                     Set-AutoPermanentFlags -Policy $resolvedPolicy.ResolvedPolicy -RoleName $PolicyDefinition.RoleName }
        'template' { $templateName = $PolicyDefinition.template; if (-not $templateName) { $templateName = $PolicyDefinition.policyTemplate }
                     if (-not $templateName) { throw 'Template policy source specified but neither template nor policyTemplate property is provided' }
                     if (-not $Templates.ContainsKey($templateName)) { throw "Template '$templateName' not found in PolicyTemplates" }
                     $templatePolicy = $Templates[$templateName]

                     # Merge template policy with PolicyDefinition, allowing template to provide defaults for empty values
                     $mergedPolicy = @{}
                     # Start with template values as defaults
                     foreach ($templateProp in $templatePolicy.PSObject.Properties) {
                         $mergedPolicy[$templateProp.Name] = $templateProp.Value
                     }
                     # Override with non-empty PolicyDefinition values
                     foreach ($property in $PolicyDefinition.PSObject.Properties) {
                         if ($null -ne $property.Value -and $property.Value -ne '' -and $property.Name -notin @('policySource', 'template', 'policyTemplate', 'policy')) {
                             $mergedPolicy[$property.Name] = $property.Value
                         }
                     }
                     $resolvedPolicy.ResolvedPolicy = [PSCustomObject]$mergedPolicy
                     Set-AutoPermanentFlags -Policy $resolvedPolicy.ResolvedPolicy -RoleName $PolicyDefinition.RoleName }
        default    { throw "Unknown policySource: $policySource" }
    }

    return [PSCustomObject]$resolvedPolicy
}