source/Private/TenantOnboardingOperations.ps1


function Set-RJTenantServicePrincipal {
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([System.Collections.Hashtable])]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$FeatureResolution
    )

    Write-Verbose "Creating/updating service principals for features: $($FeatureResolution.FeatureNames -join ', ')"

    $mandatoryCount = (Get-RJMandatoryFeatureList).Count
    $totalFeatures = $FeatureResolution.FeatureNames.Count
    $optionalFeatures = $totalFeatures - $mandatoryCount

    Write-Verbose "Feature statistics: $totalFeatures features ($mandatoryCount mandatory, $optionalFeatures optional), $($FeatureResolution.AllPermissionsCount) total permissions"

    if ($PSCmdlet.ShouldProcess("Tenant Service Principals", "Configure permissions for $($FeatureResolution.FeatureNames.Count) features")) {
        # Use shared analysis logic to determine what changes are needed
        $analysis = Get-RJTenantPermissionAnalysis -FeatureResolution $FeatureResolution
        $msGraphSp = $analysis.MsGraphServicePrincipal

        $processedApps = @()

        foreach ($featureAnalysis in $analysis.FeatureAnalysis) {
            $appId = $featureAnalysis.AppId
            Write-Information "Processing feature: $($featureAnalysis.FeatureName) (AppId: $appId)"
            Write-Verbose "Permissions for feature $($featureAnalysis.FeatureName): $($featureAnalysis.DesiredPermissions -join ', ')"

            # If no changes are needed, log and continue
            if ($featureAnalysis.FeatureChangeType -eq "NoChanges") {
                Write-Information "Feature: $($featureAnalysis.FeatureName) - No changes required"

                $processedApps += @{
                    FeatureName      = $featureAnalysis.FeatureName
                    AppId            = $appId
                    ServicePrincipal = @{ displayName = $featureAnalysis.ServicePrincipalName; id = $featureAnalysis.ServicePrincipalId }
                    PermissionCount  = $featureAnalysis.DesiredPermissions.Count
                    Success          = $true
                    ChangeType       = $featureAnalysis.FeatureChangeType
                }
                continue
            }

            Write-Verbose "Changes needed for feature $($featureAnalysis.FeatureName): Add=$($featureAnalysis.FeaturePermissionsToAdd.Count), Remove=$($featureAnalysis.FeaturePermissionsToRemove.Count)"

            # Convert feature permissions to app role assignments
            $assignmentsToAdd = @()
            foreach ($permission in $featureAnalysis.FeaturePermissionsToAdd) {
                $appRole = $msGraphSp.appRoles | Where-Object { $_.value -eq $permission }
                if ($appRole) {
                    $assignmentsToAdd += @{
                        ResourceId     = $msGraphSp.id
                        AppRoleId      = $appRole.id
                        PermissionName = $permission
                    }
                }
            }

            $assignmentsToRemove = @()

            # Use the permission details with assignment IDs from the analysis
            foreach ($permissionDetail in $featureAnalysis.FeaturePermissionDetailsToRemove) {
                $assignmentsToRemove += @{
                    ResourceId     = $permissionDetail.ResourceId
                    AppRoleId      = $permissionDetail.AppRoleId
                    PermissionName = $permissionDetail.PermissionName
                    PrincipalId    = $permissionDetail.PrincipalId
                    Id             = $permissionDetail.AppRoleAssignmentId
                }
            }

            # Execute the actual work
            Write-Verbose "Creating/updating service principal and assigning permissions for feature: $($featureAnalysis.FeatureName)"
            $servicePrincipal = Set-RJServicePrincipal -AppId $appId -AssignmentsToAdd $assignmentsToAdd -AssignmentsToRemove $assignmentsToRemove

            Write-Information "Feature $($featureAnalysis.FeatureName) configured: $($assignmentsToAdd.Count) permissions assigned, $($assignmentsToRemove.Count) permissions removed"

            $processedApps += @{
                FeatureName      = $featureAnalysis.FeatureName
                AppId            = $appId
                ServicePrincipal = $servicePrincipal
                PermissionCount  = $assignmentsToAdd.Count
                Success          = $true
                ChangeType       = $featureAnalysis.FeatureChangeType
            }
        }

        # Process orphaned service principals
        if ($analysis.OrphanedServicePrincipals -and $analysis.OrphanedServicePrincipals.Count -gt 0) {
            Write-Information "Processing $($analysis.OrphanedServicePrincipals.Count) orphaned service principals"

            foreach ($orphanedSP in $analysis.OrphanedServicePrincipals) {
                Write-Information "Removing orphaned service principal: $($orphanedSP.ServicePrincipalName)"

                Remove-ServicePrincipal -Id $orphanedSP.ServicePrincipalId
                Write-Information "Successfully removed orphaned service principal: $($orphanedSP.ServicePrincipalName)"

                $processedApps += @{
                    FeatureName      = "Orphaned Service Principal"
                    AppId            = $orphanedSP.AppId
                    ServicePrincipal = @{ displayName = $orphanedSP.ServicePrincipalName; id = $orphanedSP.ServicePrincipalId }
                    PermissionCount  = 0
                    Success          = $true
                    ChangeType       = "RemoveServicePrincipal"
                }
            }
        }

        return @{
            FeatureResolution       = $FeatureResolution
            ProcessedApps           = $processedApps
            MsGraphServicePrincipal = $msGraphSp
            Success                 = $true
        }
    }
    else {
        # WhatIf mode - return empty result
        return @{
            FeatureResolution       = $FeatureResolution
            ProcessedApps           = @()
            MsGraphServicePrincipal = $null
            Success                 = $true
        }
    }
}

function Remove-RJLegacyApplication {
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([System.Collections.Hashtable])]
    param()

    Write-Verbose "Removing legacy applications"

    $legacyApps = Get-RJLegacyAppList

    if (-not $legacyApps -or $legacyApps.Count -eq 0) {
        Write-Information "No legacy applications configured for cleanup."
        return @{
            ProcessedApps = @()
            Success       = $true
        }
    }

    Write-Information "Checking for $($legacyApps.Count) legacy applications to clean up"

    if ($PSCmdlet.ShouldProcess("Legacy Applications", "Remove $($legacyApps.Count) legacy applications")) {
        $processedApps = @()

        foreach ($legacyApp in $legacyApps) {
            Write-Verbose "Processing legacy app: $($legacyApp.DisplayName) (AppId: $($legacyApp.AppId))"

            # Use shared analysis logic to determine what action is needed
            $legacyDiff = Get-RJLegacyAppDiff -LegacyApp $legacyApp

            # Create the result object based on the analysis
            $result = @{
                AppId       = $legacyDiff.AppId
                DisplayName = $legacyDiff.DisplayName
                ObjectId    = $legacyDiff.ServicePrincipalId
                Action      = $legacyDiff.Action
                Success     = $true
                Error       = $legacyDiff.Error
            }

            # Handle each action type
            switch ($legacyDiff.Action) {
                "Remove" {
                    Write-Information "Found legacy app: $($legacyDiff.ServicePrincipalName) (ObjectId: $($legacyDiff.ServicePrincipalId))"
                    Write-Verbose "Removing legacy service principal: $($legacyDiff.ServicePrincipalName)"
                    Remove-ServicePrincipal -Id $legacyDiff.ServicePrincipalId

                    Write-Information "Successfully removed legacy app: $($legacyDiff.ServicePrincipalName)"
                    $result.Action = 'Removed'
                    $result.DisplayName = $legacyDiff.ServicePrincipalName  # Use actual name from service principal
                }

                "NotFound" {
                    Write-Verbose "Legacy app not found (already removed): $($legacyApp.DisplayName)"
                    $result.Action = 'NotFound'
                }

                "Error" {
                    Write-Warning "Error checking legacy app $($legacyApp.AppId): $($legacyDiff.Error)"
                    $result.Success = $false
                }
            }

            $processedApps += , @($result)
        }

        return @{
            ProcessedApps = $processedApps
            Success       = $true
        }
    }
    else {
        # WhatIf mode - return empty result
        return @{
            ProcessedApps = @()
            Success       = $true
        }
    }
}

function Get-RJLegacyAppList {

    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param()

    $config = Get-RJConfiguration
    # Use comma operator to prevent PowerShell from unwrapping single-element arrays
    return , $config.LegacyApps
}

function Open-RJPortalIfNewServicePrincipal {

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [array]$ProcessedApps
    )

    $realmJoinPortalAppId = 'b0130885-16be-4c6f-83de-5b1042b5d2e3'

    # Check if RealmJoin Portal service principal was newly created
    $realmJoinApp = $ProcessedApps | Where-Object {
        $_.AppId -eq $realmJoinPortalAppId -and $_.ChangeType -eq 'CreateServicePrincipal'
    }

    if ($realmJoinApp) {
        Write-Information "Opening RealmJoin Portal in browser for newly created service principal"

        try {
            $portalUrl = Get-RJPortalUrl
            Start-Process $portalUrl
            Write-Information "RealmJoin Portal opened: $portalUrl"
        }
        catch {
            Write-Warning "Failed to open RealmJoin Portal in browser: $($_.Exception.Message)"
        }
    }
    else {
        Write-Verbose "RealmJoin Portal service principal was not newly created - skipping browser launch"
    }
}

function Invoke-RJTenantRefresh {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Token,

        [Parameter()]
        [string[]]$OptionalFeatures = @()
    )

    $body = @{
        token            = $Token
        optionalFeatures = $OptionalFeatures
    } | ConvertTo-Json

    try {
        Write-Information "Refreshing RealmJoin tenant configuration. This may take a few moments"
        Start-Sleep -Seconds 10  # Short delay to ensure previous changes are settled
        Invoke-RestMethod -Uri (Get-RJCustomerApiUrl) -Method Post -Body $body -ContentType "application/json"
        Write-Information "Tenant refresh completed successfully"

        Write-Information "Waiting for API to complete processing..."
        Start-Sleep -Seconds 5
    }
    catch {
        $statusCode = $null
        $statusDescription = $null
        if ($_.Exception.Response) {
            $statusCode = $_.Exception.Response.StatusCode.Value__
            $statusDescription = $_.Exception.Response.StatusDescription
        }

        if ($statusCode -eq 401) {
            Write-Warning "Authentication failed (401). The token may be invalid or expired."
            Show-RJManualTokenInput -OptionalFeatures $OptionalFeatures
        }
        else {
            Write-Warning ("StatusCode: {0}" -f $statusCode)
            Write-Warning ("StatusDescription: {0}" -f $statusDescription)
            Write-Warning ("ErrorMessage: {0}" -f $_.Exception.Message)
        }
    }
}

function Show-RJManualTokenInput {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string[]]$OptionalFeatures
    )

    Write-Host "Press any key to continue..." -ForegroundColor Gray
    $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

    $choice = Show-RJGenericMenu -Title "Token Authentication Failed" -Prompt "What would you like to do?" -Options $TokenRetryOptions

    switch ($choice) {
        'Manual' {
            $newToken = Read-Host "Enter the new token"
            if (-not [string]::IsNullOrWhiteSpace($newToken)) {
                Invoke-RJTenantRefresh -Token $newToken -OptionalFeatures $OptionalFeatures
            }
            else {
                Write-Host "No token entered. Skipping token verification." -ForegroundColor Yellow
            }
        }
        'Portal' {
            try {
                $portalFeaturesUrl = Get-RJPortalFeaturesUrl
                Start-Process $portalFeaturesUrl
                Write-Host "Portal opened: $portalFeaturesUrl" -ForegroundColor Green
            }
            catch {
                Write-Host "Failed to open portal: $($_.Exception.Message)" -ForegroundColor Red
            }

            $newToken = Read-Host "Enter the token from the portal"
            if (-not [string]::IsNullOrWhiteSpace($newToken)) {
                Invoke-RJTenantRefresh -Token $newToken -OptionalFeatures $OptionalFeatures
            }
            else {
                Write-Host "No token entered. Skipping token verification." -ForegroundColor Yellow
            }
        }
        'Skip' {
            Write-Host "Skipping token verification as requested." -ForegroundColor Gray
        }
        default {
            Write-Host "Skipping token verification." -ForegroundColor Gray
        }
    }
}