source/Private/WhatIfAnalysis.ps1

function Test-RJTenantAlreadyConfigured {
    [CmdletBinding()]
    [OutputType([bool])]
    param()

    $allFeatures = Get-RJFeatureDefinitionList
    $uniqueAppIds = $allFeatures.AppId | Sort-Object -Unique

    foreach ($appId in $uniqueAppIds) {
        $servicePrincipal = Invoke-RJMgGraphCommand {
            Get-ServicePrincipalByAppId -AppId $appId
        }

        if ($servicePrincipal) {
            return $true
        }
    }
    return $false
}

function Get-RJServicePrincipalPermissionsList {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter(Mandatory)]
        [string]$ServicePrincipalId,

        [Parameter(Mandatory)]
        [PSCustomObject]$MsGraphServicePrincipal
    )

    $currentAssignments = Get-CurrentAppRoleAssignmentList -ServicePrincipalId $ServicePrincipalId
    $permissionDetails = @()

    foreach ($assignment in $currentAssignments) {
        if ($assignment.ResourceId -eq $MsGraphServicePrincipal.Id) {
            $appRole = $MsGraphServicePrincipal.AppRoles | Where-Object { $_.Id -eq $assignment.AppRoleId }
            if ($appRole) {
                $permissionDetails += [PSCustomObject]@{
                    PermissionName = $appRole.Value
                    AppRoleAssignmentId = $assignment.Id
                    AppRoleId = $assignment.AppRoleId
                    ResourceId = $assignment.ResourceId
                    PrincipalId = $assignment.PrincipalId
                }
            }
        }
    }

    return ,@($permissionDetails)
}

function Get-RJCurrentConfiguration {
    [CmdletBinding()]
    param()

    Write-Verbose "Analyzing current RealmJoin configuration"

    $allFeatures = Get-RJFeatureDefinitionList

    $msGraphSp = Get-ServicePrincipalByAppId -AppId $MSGraphAppId
    if (-not $msGraphSp) {
        throw "Microsoft Graph service principal not found"
    }

    $configuredFeatures = @()
    $uniqueAppIds = $allFeatures | Select-Object -ExpandProperty AppId -Unique

    foreach ($appId in $uniqueAppIds) {
        $sp = Get-ServicePrincipalByAppId -AppId $appId
        if ($sp) {
            Write-Verbose "Found service principal for AppId: $appId"

            $permissionDetails = Get-RJServicePrincipalPermissionsList -ServicePrincipalId $sp.Id -MsGraphServicePrincipal $msGraphSp
            $currentPermissions = $permissionDetails | ForEach-Object { $_.PermissionName }

            $featuresForApp = $allFeatures | Where-Object { $_.AppId -eq $appId }

            foreach ($feature in $featuresForApp) {
                $hasAnyPermissions = $false
                foreach ($perm in $feature.Permissions) {
                    if ($perm -in $currentPermissions) {
                        $hasAnyPermissions = $true
                        break
                    }
                }

                if (-not $hasAnyPermissions -and $feature.ReadOnlyPermissions) {
                    foreach ($perm in $feature.ReadOnlyPermissions) {
                        if ($perm -in $currentPermissions) {
                            $hasAnyPermissions = $true
                            break
                        }
                    }
                }

                if ($hasAnyPermissions) {
                    # Calculate feature-specific current permissions (intersection of SP permissions and feature permissions)
                    $featureCurrentPermissionDetails = @()
                    foreach ($perm in $feature.Permissions) {
                        $permissionDetail = $permissionDetails | Where-Object { $_.PermissionName -eq $perm }
                        if ($permissionDetail) {
                            $featureCurrentPermissionDetails += $permissionDetail
                        }
                    }
                    if ($feature.ReadOnlyPermissions) {
                        foreach ($perm in $feature.ReadOnlyPermissions) {
                            $permissionDetail = $permissionDetails | Where-Object { $_.PermissionName -eq $perm }
                            if ($permissionDetail -and $permissionDetail.PermissionName -notin ($featureCurrentPermissionDetails | ForEach-Object { $_.PermissionName })) {
                                $featureCurrentPermissionDetails += $permissionDetail
                            }
                        }
                    }

                    $configuredFeatures += [PSCustomObject]@{
                        Name                        = $feature.Name
                        AppId                       = $appId
                        ServicePrincipalId          = $sp.Id
                        ServicePrincipalName        = $sp.DisplayName
                        IsMandatory                 = $feature.IsMandatory
                        CurrentPermissions          = $featureCurrentPermissionDetails | ForEach-Object { $_.PermissionName }
                        CurrentPermissionDetails    = $featureCurrentPermissionDetails
                        ExpectedPermissions         = $feature.Permissions
                        ExpectedReadOnlyPermissions = $feature.ReadOnlyPermissions
                    }
                }
            }
        }
    }

    $legacyApps = @()
    $legacyAppList = Get-RJLegacyAppList -ErrorAction SilentlyContinue

    if ($legacyAppList) {
        foreach ($legacyApp in $legacyAppList) {
            $sp = Get-ServicePrincipalByAppId -AppId $legacyApp.AppId
            if ($sp) {
                $legacyApps += [PSCustomObject]@{
                    DisplayName        = $sp.DisplayName
                    AppId              = $legacyApp.AppId
                    ServicePrincipalId = $sp.Id
                }
            }
        }
    }

    return [PSCustomObject]@{
        ConfiguredFeatures      = $configuredFeatures
        LegacyApplications      = $legacyApps
        TotalFeatures           = $configuredFeatures.Count
        TotalLegacyApps         = $legacyApps.Count
        MsGraphServicePrincipal = $msGraphSp
    }
}

function Get-RJOrphanedServicePrincipalsList {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject[]]$FeatureAnalysis,

        [Parameter(Mandatory)]
        [PSCustomObject]$CurrentConfig
    )

    $orphanedServicePrincipals = @()

    # Group features by AppId to check final permission state
    $appIdGroups = $FeatureAnalysis | Group-Object -Property AppId

    foreach ($appGroup in $appIdGroups) {
        $appId = $appGroup.Name
        $featuresForApp = $appGroup.Group

        # Get service principal info (if exists)
        $spInfo = $featuresForApp | Where-Object { $_.ServicePrincipalExists } | Select-Object -First 1

        if ($spInfo -and $spInfo.ServicePrincipalId) {
            # Calculate total permissions being removed vs added for this SP
            $totalPermissionsToRemove = ($featuresForApp | ForEach-Object { $_.FeaturePermissionsToRemove }) | Select-Object -Unique
            $totalPermissionsToAdd = ($featuresForApp | ForEach-Object { $_.FeaturePermissionsToAdd }) | Select-Object -Unique

            # Get current SP permissions from any feature for this app
            $currentSPPermissions = ($CurrentConfig.ConfiguredFeatures | Where-Object { $_.AppId -eq $appId } | ForEach-Object { $_.CurrentPermissions }) | Select-Object -Unique

            # Calculate final permissions: current + add - remove
            $finalPermissions = ($currentSPPermissions + $totalPermissionsToAdd) | Where-Object { $_ -notin $totalPermissionsToRemove } | Select-Object -Unique

            # If no permissions remain, SP becomes orphaned
            if ($finalPermissions.Count -eq 0 -and $currentSPPermissions.Count -gt 0) {
                $orphanedServicePrincipals += [PSCustomObject]@{
                    AppId = $appId
                    ServicePrincipalId = $spInfo.ServicePrincipalId
                    ServicePrincipalName = $spInfo.ServicePrincipalName
                }
            }
        }
    }

    return ,@($orphanedServicePrincipals)
}

function Get-RJFeaturePermissionsList {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Feature,

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

        [bool]$IsIncrementalMode,

        [string[]]$FeaturesToAdd,

        [PSCustomObject]$CurrentConfig,

        [bool]$ReadOnly
    )

    if ($IsIncrementalMode) {
        # Incremental mode: use current permissions for existing features, apply ReadOnly only to new features
        $isNewFeature = $FeatureName -in $FeaturesToAdd
        $isExistingFeature = $null -ne ($CurrentConfig.ConfiguredFeatures | Where-Object { $_.Name -eq $FeatureName })

        if ($isExistingFeature -and -not $isNewFeature) {
            # Existing feature: use current permissions from CurrentConfig
            $currentFeature = $CurrentConfig.ConfiguredFeatures | Where-Object { $_.Name -eq $FeatureName }
            return ,@($currentFeature.CurrentPermissions)
        } else {
            # New feature: apply ReadOnly logic
            if ($ReadOnly -and $Feature.ReadOnlyPermissions) {
                return ,@($Feature.ReadOnlyPermissions)
            } else {
                return ,@($Feature.Permissions)
            }
        }
    } else {
        # Traditional mode: apply ReadOnly to all features
        if ($ReadOnly -and $Feature.ReadOnlyPermissions) {
            return ,@($Feature.ReadOnlyPermissions)
        } else {
            return ,@($Feature.Permissions)
        }
    }
}

function Get-RJTenantPermissionAnalysis {
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$FeatureResolution
    )

    Write-Verbose "Analyzing service principal permissions for features: $($FeatureResolution.FeatureNames -join ', ')"

    $currentConfig = Get-RJCurrentConfiguration
    $msGraphSp = $currentConfig.MsGraphServicePrincipal

    # Build comprehensive feature analysis by checking both desired and current features
    $featureAnalysis = @()

    # Get all feature names we need to analyze (both desired and currently configured)
    $desiredFeatureNames = $FeatureResolution.FeatureNames
    $currentFeatureNames = $currentConfig.ConfiguredFeatures | ForEach-Object { $_.Name }
    $allFeatureNames = ($desiredFeatureNames + $currentFeatureNames) | Sort-Object -Unique

    foreach ($featureName in $allFeatureNames) {
        $feature = Get-RJFeatureByName -FeatureName $featureName
        if ($feature) {
            $isDesired = $featureName -in $desiredFeatureNames

            $featurePermissions = if ($isDesired) {
                # Get the actual resolved permissions for this feature
                Get-RJFeaturePermissionsList -Feature $feature -FeatureName $featureName -IsIncrementalMode $FeatureResolution.IsIncrementalMode -FeaturesToAdd $FeatureResolution.FeaturesToAdd -CurrentConfig $currentConfig -ReadOnly $FeatureResolution.ReadOnly
            } else {
                @()  # Not desired, so no permissions should be assigned
            }

            # Find current feature data for this specific feature
            $currentFeature = $currentConfig.ConfiguredFeatures | Where-Object {
                $_.AppId -eq $feature.AppId -and $_.Name -eq $featureName
            } | Select-Object -First 1

            # Check if service principal exists (any feature with same App ID)
            $servicePrincipalExists = $null -ne ($currentConfig.ConfiguredFeatures | Where-Object { $_.AppId -eq $feature.AppId } | Select-Object -First 1)
            $servicePrincipalInfo = $currentConfig.ConfiguredFeatures | Where-Object { $_.AppId -eq $feature.AppId } | Select-Object -First 1

            $featureAnalysisItem = [PSCustomObject]@{
                FeatureName                = $featureName
                AppId                      = $feature.AppId
                ServicePrincipalExists     = $servicePrincipalExists
                ServicePrincipalName       = if ($servicePrincipalInfo) { $servicePrincipalInfo.ServicePrincipalName } else { "N/A" }
                ServicePrincipalId         = if ($servicePrincipalInfo) { $servicePrincipalInfo.ServicePrincipalId } else { $null }
                DesiredPermissions         = $featurePermissions
                CurrentPermissions         = if ($currentFeature) { $currentFeature.CurrentPermissions } else { @() }
                CurrentPermissionDetails   = if ($currentFeature) { $currentFeature.CurrentPermissionDetails } else { @() }
                FeatureChangeType          = ""
                FeaturePermissionsToAdd    = @()
                FeaturePermissionsToRemove = @()
                IsMandatory                = $feature.IsMandatory
                IsDesired                  = $isDesired
            }

            if (-not $servicePrincipalExists) {
                if ($isDesired) {
                    $featureAnalysisItem.FeatureChangeType = "CreateServicePrincipal"
                    $featureAnalysisItem.FeaturePermissionsToAdd = $featurePermissions
                } else {
                    # Feature not desired and SP doesn't exist - nothing to do
                    $featureAnalysisItem.FeatureChangeType = "NoChanges"
                }
            } else {
                if ($isDesired) {
                    # Feature is desired - add/modify permissions as needed
                    $currentFeaturePermissions = if ($currentFeature) { $currentFeature.CurrentPermissions } else { @() }
                    $currentFeaturePermissionDetails = if ($currentFeature) { $currentFeature.CurrentPermissionDetails } else { @() }

                    $permissionsToAdd = $featurePermissions | Where-Object { $_ -notin $currentFeaturePermissions }
                    $permissionsToRemove = $currentFeaturePermissions | Where-Object { $_ -notin $featurePermissions }

                    # Build detailed removal objects with assignment IDs
                    $permissionDetailsToRemove = @()
                    foreach ($permissionName in $permissionsToRemove) {
                        $permissionDetail = $currentFeaturePermissionDetails | Where-Object { $_.PermissionName -eq $permissionName }
                        if ($permissionDetail) {
                            $permissionDetailsToRemove += $permissionDetail
                        }
                    }

                    $featureAnalysisItem.FeaturePermissionsToAdd = $permissionsToAdd
                    $featureAnalysisItem.FeaturePermissionsToRemove = $permissionsToRemove
                    $featureAnalysisItem | Add-Member -NotePropertyName "FeaturePermissionDetailsToRemove" -NotePropertyValue $permissionDetailsToRemove

                    if ($permissionsToAdd.Count -eq 0 -and $permissionsToRemove.Count -eq 0) {
                        $featureAnalysisItem.FeatureChangeType = "NoChanges"
                    } elseif ($permissionsToAdd.Count -gt 0 -and $permissionsToRemove.Count -eq 0) {
                        $featureAnalysisItem.FeatureChangeType = "AddPermissions"
                    } elseif ($permissionsToAdd.Count -eq 0 -and $permissionsToRemove.Count -gt 0) {
                        $featureAnalysisItem.FeatureChangeType = "RemovePermissions"
                    } else {
                        $featureAnalysisItem.FeatureChangeType = "ModifyPermissions"
                    }
                } else {
                    # Feature is not desired but currently configured - remove all its permissions
                    if ($currentFeature) {
                        $featureAnalysisItem.FeaturePermissionsToRemove = $currentFeature.CurrentPermissions
                        $featureAnalysisItem.FeatureChangeType = "RemoveFeature"

                        # Add detailed removal objects with assignment IDs for complete removal
                        $featureAnalysisItem | Add-Member -NotePropertyName "FeaturePermissionDetailsToRemove" -NotePropertyValue $currentFeature.CurrentPermissionDetails
                    } else {
                        $featureAnalysisItem.FeatureChangeType = "NoChanges"
                    }
                }
            }

            $featureAnalysis += $featureAnalysisItem
        }
    }

    # Detect orphaned service principals using the extracted function
    $orphanedServicePrincipals = Get-RJOrphanedServicePrincipalsList -FeatureAnalysis $featureAnalysis -CurrentConfig $currentConfig

    return @{
        FeatureAnalysis            = $featureAnalysis
        OrphanedServicePrincipals  = $orphanedServicePrincipals
        MsGraphServicePrincipal    = $msGraphSp
        CurrentConfiguration       = $currentConfig
    }
}

function Show-RJFeatureAnalysis {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject[]]$FeatureAnalysis,

        [Parameter()]
        [PSCustomObject[]]$OrphanedServicePrincipals
    )

    Write-Host ""
    Write-Host "Feature Analysis:" -ForegroundColor Cyan
    Write-Host ("=" * 80) -ForegroundColor Gray

    $featuresByAppId = $FeatureAnalysis | Group-Object -Property AppId

    foreach ($appGroup in $featuresByAppId) {
        $appId = $appGroup.Name
        $appFeatures = $appGroup.Group
        $firstFeature = $appFeatures[0]

        $displayName = if ($firstFeature.ServicePrincipalExists) { "($($firstFeature.ServicePrincipalName))" } else { "" }
        Write-Host ""
        Write-Host "AppId: $appId $displayName" -ForegroundColor Cyan
        Write-Host ("-" * 60) -ForegroundColor Gray

        if ($appFeatures.Count -gt 1) {
            $spAction = if (-not $firstFeature.ServicePrincipalExists) { "will be CREATED" } else { "will be UPDATED" }
            Write-Host "→ Service principal $spAction" -ForegroundColor Yellow
            Write-Host ""
        }

        foreach ($feature in $appFeatures) {
            $mandatoryText = if ($feature.IsMandatory) { " (Mandatory)" } else { "" }
            Write-Host " 📋 Feature: $($feature.FeatureName)$mandatoryText" -ForegroundColor White

            switch ($feature.FeatureChangeType) {
                "NoChanges" {
                    Write-Host " ✓ No changes required" -ForegroundColor Green
                    if ($feature.DesiredPermissions.Count -gt 0) {
                        Write-Host " Current permissions ($($feature.DesiredPermissions.Count)): $($feature.DesiredPermissions -join ', ')" -ForegroundColor Gray
                    }
                }

                "CreateServicePrincipal" {
                    Write-Host " → Will assign permissions (new service principal)" -ForegroundColor Yellow
                    if ($feature.FeaturePermissionsToAdd.Count -gt 0) {
                        Write-Host " Permissions to assign ($($feature.FeaturePermissionsToAdd.Count)):" -ForegroundColor Yellow
                        foreach ($permission in $feature.FeaturePermissionsToAdd) {
                            Write-Host " + $permission" -ForegroundColor Green
                        }
                    }
                }

                "AddPermissions" {
                    Write-Host " → Will add permissions" -ForegroundColor Yellow
                    if ($feature.FeaturePermissionsToAdd.Count -gt 0) {
                        Write-Host " Permissions to add ($($feature.FeaturePermissionsToAdd.Count)):" -ForegroundColor Yellow
                        foreach ($permission in $feature.FeaturePermissionsToAdd) {
                            Write-Host " + $permission" -ForegroundColor Green
                        }
                    }
                }

                "RemovePermissions" {
                    Write-Host " → Will remove permissions" -ForegroundColor Yellow
                    if ($feature.FeaturePermissionsToRemove.Count -gt 0) {
                        Write-Host " Permissions to remove ($($feature.FeaturePermissionsToRemove.Count)):" -ForegroundColor Yellow
                        foreach ($permission in $feature.FeaturePermissionsToRemove) {
                            Write-Host " - $permission" -ForegroundColor Red
                        }
                    }
                }

                "ModifyPermissions" {
                    Write-Host " → Will modify permissions" -ForegroundColor Yellow
                    if ($feature.FeaturePermissionsToAdd.Count -gt 0) {
                        Write-Host " Permissions to add ($($feature.FeaturePermissionsToAdd.Count)):" -ForegroundColor Yellow
                        foreach ($permission in $feature.FeaturePermissionsToAdd) {
                            Write-Host " + $permission" -ForegroundColor Green
                        }
                    }
                    if ($feature.FeaturePermissionsToRemove.Count -gt 0) {
                        Write-Host " Permissions to remove ($($feature.FeaturePermissionsToRemove.Count)):" -ForegroundColor Yellow
                        foreach ($permission in $feature.FeaturePermissionsToRemove) {
                            Write-Host " - $permission" -ForegroundColor Red
                        }
                    }
                }

                "RemoveFeature" {
                    Write-Host " → Will remove feature" -ForegroundColor Red
                    if ($feature.FeaturePermissionsToRemove.Count -gt 0) {
                        Write-Host " Permissions to remove ($($feature.FeaturePermissionsToRemove.Count)):" -ForegroundColor Red
                        foreach ($permission in $feature.FeaturePermissionsToRemove) {
                            Write-Host " - $permission" -ForegroundColor Red
                        }
                    }
                }
            }

            if ($feature -ne $appFeatures[-1]) {
                Write-Host ""
            }
        }
    }

    # Show orphaned service principals if any
    if ($OrphanedServicePrincipals -and $OrphanedServicePrincipals.Count -gt 0) {
        Write-Host ""
        Write-Host "Orphaned Service Principals:" -ForegroundColor Magenta
        Write-Host ("=" * 80) -ForegroundColor Gray

        foreach ($orphanedSP in $OrphanedServicePrincipals) {
            Write-Host "→ [REMOVE] $($orphanedSP.ServicePrincipalName)" -ForegroundColor Magenta
            Write-Host " AppId: $($orphanedSP.AppId)" -ForegroundColor Gray
            Write-Host " Reason: No remaining permissions after feature removal" -ForegroundColor Gray
            Write-Host ""
        }
    }
}

function Get-RJLegacyAppDiff {

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$LegacyApp
    )

    Write-Verbose "Analyzing legacy app: $($LegacyApp.DisplayName) (AppId: $($LegacyApp.AppId))"

    $diff = [PSCustomObject]@{
        AppId                  = $LegacyApp.AppId
        DisplayName            = $LegacyApp.DisplayName
        ServicePrincipalExists = $false
        ServicePrincipalName   = $null
        ServicePrincipalId     = $null
        Action                 = "NotFound"
        Error                  = $null
        RequiresRemoval        = $false
    }

    $servicePrincipal = Get-ServicePrincipalByAppId -AppId $LegacyApp.AppId

    if ($servicePrincipal) {
        $diff.ServicePrincipalExists = $true
        $diff.ServicePrincipalName = $servicePrincipal.displayName
        $diff.ServicePrincipalId = $servicePrincipal.id
        $diff.Action = "Remove"
        $diff.RequiresRemoval = $true

        Write-Verbose "Legacy app found and marked for removal: $($servicePrincipal.displayName)"
    }
    else {
        Write-Verbose "Legacy app not found (already removed): $($LegacyApp.DisplayName)"
    }

    Write-Verbose "Legacy app analysis complete for $($LegacyApp.AppId): Action=$($diff.Action), RequiresRemoval=$($diff.RequiresRemoval)"
    return $diff
}

function Show-RJLegacyAppAnalysis {

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject[]]$Analysis
    )

    if (-not $Analysis -or $Analysis.Count -eq 0) {
        return
    }

    # Only show analysis if there are Remove or Error actions
    $actionableApps = $Analysis | Where-Object { $_.Action -eq "Remove" -or $_.Action -eq "Error" }

    if ($actionableApps.Count -eq 0) {
        return
    }

    Write-Host ""
    Write-Host "Legacy Applications Analysis:" -ForegroundColor Cyan
    Write-Host ("=" * 80) -ForegroundColor Gray

    foreach ($app in $actionableApps) {
        switch ($app.Action) {
            "Remove" {
                Write-Host "→ [REMOVE] $($app.ServicePrincipalName)" -ForegroundColor Red
                Write-Host " AppId: $($app.AppId)" -ForegroundColor Gray
                Write-Host " ObjectId: $($app.ServicePrincipalId)" -ForegroundColor Gray
            }

            "Error" {
                Write-Host "✗ [ERROR] $($app.DisplayName)" -ForegroundColor Magenta
                Write-Host " AppId: $($app.AppId)" -ForegroundColor Gray
                Write-Host " Error: $($app.Error)" -ForegroundColor Red
            }
        }
        Write-Host ""
    }
}