<#
.SYNOPSIS
    Batch modernization of multiple AnywhereNow UCC sites for MC1117115 compliance.
.DESCRIPTION
    Processes multiple UCC sites from a CSV file or discovers them automatically.
    Prerequisites:
    1. ucc-dashboard-spfx.sppkg must be deployed tenant-wide (run Deploy-UccDashboard.ps1 first)
    2. App registration with Sites.FullControl.All permission for automation
    Input CSV format:
    SiteUrl
    https://contoso.sharepoint.com/sites/ucc1
    https://contoso.sharepoint.com/sites/ucc2
    https://contoso.sharepoint.com/sites/parent/subsite1
.PARAMETER TenantUrl
    Admin URL of the tenant (e.g., "https://contoso-admin.sharepoint.com")
    Used for discovery mode.
.PARAMETER InputCsv
    Path to CSV file containing site URLs to process
.PARAMETER OutputPath
    Path for output reports (default: .\Reports)
.PARAMETER Mode
    - Discover: Find all UCC sites in the tenant
    - Analyze: Analyze sites from CSV without making changes
    - Modernize: Modernize sites from CSV
.PARAMETER MaxParallel
    Maximum parallel jobs (default: 5)
.PARAMETER Force
    Re-modernize sites that are already modernized. Without this, already-modernized sites are skipped.
.PARAMETER PreserveLegacyScripts
    Preserve legacy ScriptLink custom actions during modernization. Use this when testing 
    modernization on some sites while others still run on v8.3.0.1 or earlier.
    Once all sites are modernized, re-run without this parameter to complete the cleanup.
.EXAMPLE
    # Discover all UCC sites in a tenant
    .\Batch-ModernizeUccSites.ps1 -TenantUrl "https://contoso-admin.sharepoint.com" -Mode Discover -Interactive -ClientId "your-client-id"
.EXAMPLE
    # Analyze sites from CSV
    .\Batch-ModernizeUccSites.ps1 -InputCsv ".\sites.csv" -Mode Analyze -Interactive -ClientId "your-client-id"
.EXAMPLE
    # Modernize all sites in CSV
    .\Batch-ModernizeUccSites.ps1 -InputCsv ".\sites.csv" -Mode Modernize -Interactive -ClientId "your-client-id"
.NOTES
    Nested Subsites (Sub-Subsites):
    The script fully supports nested subsite structures commonly used for regional groupings:
      /sites/ucc              (site collection)
        /france               (regional grouping - skipped if no UCC lists)
          /paris-ucc          (UCC site - will be discovered and processed)
          /lyon-ucc           (UCC site - will be discovered and processed)
        /apac                 (regional grouping - skipped if no UCC lists)
          /tokyo-ucc          (UCC site - will be discovered and processed)
    
    Discovery mode uses Get-PnPSubWeb -Recurse which finds subsites at any depth.
    Sites without UCC lists (like grouping containers) are automatically skipped.
#>
[CmdletBinding()]
param(
    [string]$TenantUrl,
    [string]$InputCsv,
    [string]$OutputPath = ".\Reports",
    [Parameter(Mandatory=$true)]
    [ValidateSet("Discover", "Analyze", "Modernize")]
    [string]$Mode,
    [switch]$Interactive,
    [string]$ClientId,
    [string]$CertificatePath,
    [SecureString]$CertificatePassword,
    [string]$Tenant,
    [int]$MaxParallel = 5,
    [switch]$Force,
    [switch]$PreserveLegacyScripts,
    [string]$SiteCollectionAppCatalogUrl,
    [switch]$UseSiteCollectionAppCatalog
)
# Check PowerShell version - PnP.PowerShell 3.x requires PowerShell 7.4.6+
if ($PSVersionTable.PSVersion.Major -lt 7 -or 
    ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -lt 4)) {
    Write-Host ""
    Write-Host "ERROR: This script requires PowerShell 7.4.6 or higher." -ForegroundColor Red
    Write-Host "You are running: PowerShell $($PSVersionTable.PSVersion)" -ForegroundColor Red
    Write-Host ""
    Write-Host "Windows PowerShell 5.1 is not supported by PnP.PowerShell 3.x" -ForegroundColor Yellow
    Write-Host ""
    Write-Host "Please install PowerShell 7:" -ForegroundColor Cyan
    Write-Host "  https://aka.ms/PSWindows" -ForegroundColor White
    Write-Host ""
    Write-Host "Or run: winget install Microsoft.PowerShell" -ForegroundColor White
    Write-Host ""
    exit 1
}

$ErrorActionPreference = "Stop"

# Script version
$ScriptVersion = "8.4.1.8"
$ScriptDir = $PSScriptRoot
# ============================================================================
# Configuration
# ============================================================================
# Lists that identify a UCC site
$uccIdentifierLists = @("Agents", "Skills", "Endpoints", "Businesshours")
# ============================================================================
# Helper Functions
# ============================================================================
function Write-Banner {
    Write-Host ""
    Write-Host "╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
    Write-Host "║     AnywhereNow UCC Batch Modernization Tool v$ScriptVersion        ║" -ForegroundColor Cyan
    Write-Host "║                  MC1117115 Compliance                        ║" -ForegroundColor Cyan
    Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
    Write-Host ""
}
function Write-Info($message) { Write-Host "  [INFO] $message" -ForegroundColor Cyan }
function Write-Success($message) { Write-Host "  [OK]   $message" -ForegroundColor Green }
function Write-Warn($message) { Write-Host "  [WARN] $message" -ForegroundColor Yellow }
function Write-Err($message) { Write-Host "  [ERR]  $message" -ForegroundColor Red }

# ============================================================================
# Load configuration.xml fallback for missing parameters
# ============================================================================
# If authentication parameters are not provided on the command line, try to read them
# from configuration.xml (same pattern as UCC.Site.Creator.ps1 and Shared.ps1)
if (-not $Interactive) {
    $configPath = Join-Path $ScriptDir "configuration.xml"
    if (Test-Path $configPath) {
        Write-Info "Reading fallback parameters from configuration.xml"
        try {
            $configXml = [xml](Get-Content -Path $configPath -Raw -Encoding UTF8)
            if ($configXml.Configuration) {
                # ClientId fallback from CertificateAppId
                if ([string]::IsNullOrEmpty($ClientId) -and -not [string]::IsNullOrEmpty($configXml.Configuration.CertificateAppId)) {
                    $ClientId = $configXml.Configuration.CertificateAppId.Trim()
                    Write-Info "Using ClientId from configuration.xml"
                }
                # Tenant fallback from CertificateTenantId
                if ([string]::IsNullOrEmpty($Tenant) -and -not [string]::IsNullOrEmpty($configXml.Configuration.CertificateTenantId)) {
                    $Tenant = $configXml.Configuration.CertificateTenantId.Trim()
                    Write-Info "Using Tenant from configuration.xml"
                }
                # CertificatePath fallback
                if ([string]::IsNullOrEmpty($CertificatePath) -and -not [string]::IsNullOrEmpty($configXml.Configuration.CertificatePath)) {
                    $CertificatePath = $configXml.Configuration.CertificatePath.Trim()
                    Write-Info "Using CertificatePath from configuration.xml"
                }
                # CertificatePassword fallback (handles encrypted attribute)
                if ($null -eq $CertificatePassword) {
                    $certPwd = $null
                    $encrypted = $configXml.Configuration.CertificatePassword.encrypted
                    if ($null -ne $encrypted) {
                        $certPwd = $configXml.Configuration.CertificatePassword.InnerText
                    }
                    else {
                        $certPwd = $configXml.Configuration.CertificatePassword
                    }
                    if (-not [string]::IsNullOrEmpty($certPwd)) {
                        $certPwd = $certPwd.Trim()
                        if ($null -ne $encrypted -and $encrypted.ToLower() -eq "true") {
                            $CertificatePassword = ConvertTo-SecureString -String $certPwd -Force
                        }
                        else {
                            $CertificatePassword = ConvertTo-SecureString -String $certPwd -AsPlainText -Force
                        }
                        Write-Info "Using CertificatePassword from configuration.xml"
                    }
                }
            }
        }
        catch {
            Write-Warn "Could not read configuration.xml: $($_.Exception.Message)"
        }
    }
}

function Get-Timestamp {
    return Get-Date -Format "yyyyMMdd-HHmmss"
}
function Import-PnPModule {
    $bundledPath = Join-Path $ScriptDir "bundle\PnP.PowerShell"
    if (Test-Path $bundledPath) {
        Write-Info "Loading bundled PnP.PowerShell module (v3.1.0)..."
        Import-Module "$bundledPath\PnP.PowerShell.psd1" -Force -DisableNameChecking
        return $true
    }
    else {
        Write-Err "Bundled PnP.PowerShell module not found at: $bundledPath"
        return $false
    }
}

# Validate authentication parameters
# Note: As of September 9th 2024, the PnP Management Shell multi-tenant app has been deleted.
#       You must register your own Entra ID Application and provide the ClientId.
#       See: https://pnp.github.io/powershell/articles/registerapplication.html
if ($Interactive -and -not $ClientId) {
    Write-Err "ClientId is required for interactive authentication."
    Write-Err ""
    Write-Err "The PnP Management Shell multi-tenant app was deleted on September 9th, 2024."
    Write-Err "You must register your own Entra ID Application and provide the -ClientId parameter."
    Write-Err ""
    Write-Err "See: https://pnp.github.io/powershell/articles/registerapplication.html"
    exit 1
}

# Prompt for certificate password if using certificate auth and password not provided
if ($CertificatePath -and -not $Interactive) {
    if ($null -eq $CertificatePassword) {
        Write-Host ""
        $CertificatePassword = Read-Host -Prompt "Enter certificate password" -AsSecureString
        Write-Host ""
    }
}

function Connect-ToSite {
    param([string]$Url)
    try {
        if ($Interactive) {
            Connect-PnPOnline -Url $Url -Interactive -ClientId $ClientId
        }
        elseif ($CertificatePath -and $Tenant) {
            Connect-PnPOnline -Url $Url -ClientId $ClientId -CertificatePath $CertificatePath -CertificatePassword $CertificatePassword -Tenant $Tenant
        }
        else {
            Write-Err "No valid authentication method. Use -Interactive -ClientId or certificate parameters."
            return $false
        }
        return $true
    }
    catch {
        return $false
    }
}
# ============================================================================
# Discovery Functions
# ============================================================================
function Find-UccSites {
    param([string]$AdminUrl)
    Write-Info "Discovering UCC sites in tenant..."
    Write-Info "Admin URL: $AdminUrl"
    if (-not (Connect-ToSite -Url $AdminUrl)) {
        Write-Err "Failed to connect to admin site"
        return @()
    }
    $uccSites = @()
    # Get all site collections
    Write-Info "Retrieving site collections..."
    $sites = Get-PnPTenantSite -Detailed
    Write-Info "Found $($sites.Count) site collections"
    $processed = 0
    foreach ($site in $sites) {
        $processed++
        Write-Progress -Activity "Scanning sites" -Status "$processed / $($sites.Count)" -PercentComplete (($processed / $sites.Count) * 100)
        try {
            # Connect to the site
            if (-not (Connect-ToSite -Url $site.Url)) {
                continue
            }
            # Check if it has UCC lists
            $lists = Get-PnPList -ErrorAction SilentlyContinue
            $hasUccLists = $false
            foreach ($identifier in $uccIdentifierLists) {
                if ($lists | Where-Object { $_.Title -eq $identifier }) {
                    $hasUccLists = $true
                    break
                }
            }
            if ($hasUccLists) {
                $uccSites += @{
                    Url = $site.Url
                    Title = $site.Title
                    Template = $site.Template
                    IsSubsite = $false
                }
            }
            # Check for subsites with UCC
            $subsites = Get-PnPSubWeb -Recurse -ErrorAction SilentlyContinue
            foreach ($subsite in $subsites) {
                try {
                    Connect-ToSite -Url $subsite.Url | Out-Null
                    $subLists = Get-PnPList -ErrorAction SilentlyContinue
                    $subHasUccLists = $false
                    foreach ($identifier in $uccIdentifierLists) {
                        if ($subLists | Where-Object { $_.Title -eq $identifier }) {
                            $subHasUccLists = $true
                            break
                        }
                    }
                    if ($subHasUccLists) {
                        $uccSites += @{
                            Url = $subsite.Url
                            Title = $subsite.Title
                            Template = $subsite.WebTemplate
                            IsSubsite = $true
                            ParentUrl = $site.Url
                        }
                    }
                }
                catch { }
            }
        }
        catch { }
    }
    Write-Progress -Activity "Scanning sites" -Completed
    Write-Success "Found $($uccSites.Count) UCC sites"
    return $uccSites
}
# ============================================================================
# Processing Functions
# ============================================================================
function Invoke-SiteProcess {
    param(
        [string]$SiteUrl,
        [string]$ProcessMode
    )
    $result = @{
        Url = $SiteUrl
        Status = "Unknown"
        SiteType = "Unknown"
        IsSubsite = $false
        ListsFound = 0
        ItemCount = 0
        NeedsModernization = $false
        HomepageConfigured = $false
        Error = $null
        ProcessedAt = Get-Date
    }
    try {
        # Connect
        if (-not (Connect-ToSite -Url $SiteUrl)) {
            $result.Status = "ConnectionFailed"
            $result.Error = "Could not connect to site"
            return $result
        }
        # Get site info
        $web = Get-PnPWeb -Includes ServerRelativeUrl, WelcomePage
        $site = Get-PnPSite -Includes ServerRelativeUrl
        $result.IsSubsite = $web.ServerRelativeUrl -ne $site.ServerRelativeUrl
        
        # Get all lists for site type detection and analysis
        $lists = Get-PnPList
        
        # Detect site type using same logic as Modernize-UccSite.ps1
        $hasAgentsList = $false
        $hasCreatorList = $false
        $uccListsFound = @()
        $totalItems = 0
        
        $uccLists = @(
            "Agents", "Skills", "Endpoints", "Routing", "Businesshours", "Holidays",
            "IVRQuestions", "OutboundDialerCalls", "Playlists", "PluginFlowSettings",
            "PluginSettings", "PluginAttributes", "Settings", "SettingValues", "settingsList",
            "Supervisors", "SkillsPerAgent", "VoicemailAgents", "CallClassifications",
            "CallSummary", "ReasonCodes", "PromptsConfig", "QualityMonitorConfig",
            "Modalities", "CannedResponses", "EncryptedTokens", "Version"
        )
        
        foreach ($list in $lists) {
            if ($list.Title -eq "Agents") {
                $hasAgentsList = $true
            }
            if ($list.Title -eq "Anywhere365 UCC Creator" -or $list.Title -eq "A365_UCC_Creator") {
                $hasCreatorList = $true
            }
            if ($uccLists -contains $list.Title) {
                $uccListsFound += $list.Title
                $totalItems += $list.ItemCount
            }
        }
        
        $result.ListsFound = $uccListsFound.Count
        $result.ItemCount = $totalItems
        
        # Determine site type and modernization need
        $currentHomepage = $web.WelcomePage
        
        if ($hasAgentsList) {
            # UCC Site - needs UCC Dashboard as homepage
            $result.SiteType = "UCC Site"
            $homepageIsHome = $currentHomepage -like "*SitePages/Home.aspx*"
            
            # Check if Home.aspx exists with UCC Dashboard
            $hasUccDashboard = $false
            try {
                $homePage = Get-PnPPage -Identity "Home" -ErrorAction SilentlyContinue
                if ($homePage) {
                    $webParts = Get-PnPPageComponent -Page "Home" -ErrorAction SilentlyContinue
                    $uccWebPart = $webParts | Where-Object { $_.Title -eq "UCC Dashboard" }
                    if ($uccWebPart) {
                        $hasUccDashboard = $true
                    }
                }
            }
            catch { }
            
            $result.HomepageConfigured = $homepageIsHome -and $hasUccDashboard
            $result.NeedsModernization = -not $result.HomepageConfigured
        }
        elseif ($hasCreatorList) {
            # Parent/Hub Site - needs A365_UCC_Creator list as homepage
            $result.SiteType = "Parent Site"
            $homepageIsCreatorList = $currentHomepage -like "*A365_UCC_Creator*"
            
            $result.HomepageConfigured = $homepageIsCreatorList
            $result.NeedsModernization = -not $homepageIsCreatorList
        }
        else {
            # Unknown site type - not a recognized UCC or parent site
            $result.SiteType = "Unknown (Skipped)"
            $result.NeedsModernization = $false
            $result.Status = "Skipped"
            Write-Warn "Site skipped - not a recognized UCC or parent site"
            Write-Info "  - No 'Agents' list found (required for UCC sites)"
            Write-Info "  - No 'A365_UCC_Creator' list found (required for parent sites)"
            Write-Info "  - Navigation will still be hidden to prevent inheritance by child UCC sites"
        }
        
        # If Modernize mode, call Modernize-UccSite.ps1 for all sites - including skipped
        # intermediate grouping subsites (e.g. /emea, /apac). These sites must also have
        # their left navigation hidden so that deeply nested UCC subsites beneath them do
        # not inherit a visible QuickLaunch through SharePoint's navigation inheritance chain.
        if ($ProcessMode -eq "Modernize") {
            $modernizeScript = Join-Path $ScriptDir "Modernize-UccSite.ps1"
            if (Test-Path $modernizeScript) {
                $modArgs = @{
                    SiteUrl = $SiteUrl
                    Mode = "Modernize"
                }
                if ($Force) {
                    $modArgs.Force = $true
                }
                if ($PreserveLegacyScripts) {
                    $modArgs.PreserveLegacyScripts = $true
                }
                if ($UseSiteCollectionAppCatalog) { $modArgs.UseSiteCollectionAppCatalog = $true }
                if (-not [string]::IsNullOrEmpty($SiteCollectionAppCatalogUrl)) { $modArgs.SiteCollectionAppCatalogUrl = $SiteCollectionAppCatalogUrl }
                if ($Interactive -and $ClientId) {
                    $modArgs.Interactive = $true
                    $modArgs.ClientId = $ClientId
                }
                elseif ($CertificatePath) {
                    $modArgs.ClientId = $ClientId
                    $modArgs.CertificatePath = $CertificatePath
                    $modArgs.CertificatePassword = $CertificatePassword
                    $modArgs.Tenant = $Tenant
                }
                $modResult = & $modernizeScript @modArgs
                # Preserve Skipped status for intermediate sites - they only get nav hidden
                if ($result.SiteType -eq "Unknown (Skipped)") {
                    $result.Status = "Skipped"
                }
                else {
                    $result.Status = "Modernized"
                    $result.HomepageConfigured = $true
                }
            }
            else {
                $result.Status = "Error"
                $result.Error = "Modernize-UccSite.ps1 not found"
            }
        }
        else {
            $result.Status = "Analyzed"
        }
    }
    catch {
        $result.Status = "Error"
        $result.Error = $_.Exception.Message
    }
    return $result
}
# ============================================================================
# Main Execution
# ============================================================================
Write-Banner
# Ensure output directory exists
if (-not (Test-Path $OutputPath)) {
    New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
}
# Import module
if (-not (Import-PnPModule)) {
    Write-Err "Failed to import PnP.PowerShell"
    exit 1
}
$timestamp = Get-Timestamp
$results = @()
switch ($Mode) {
    "Discover" {
        if ([string]::IsNullOrEmpty($TenantUrl)) {
            Write-Err "TenantUrl is required for Discover mode"
            exit 1
        }
        $sites = Find-UccSites -AdminUrl $TenantUrl
        # Export to CSV
        $outputFile = Join-Path $OutputPath "UccSites-$timestamp.csv"
        $sites | ForEach-Object { [PSCustomObject]$_ } | Export-Csv -Path $outputFile -NoTypeInformation
        Write-Success "Discovery complete. Found $($sites.Count) UCC sites"
        Write-Info "Output: $outputFile"
        # Summary
        $siteCollections = ($sites | Where-Object { -not $_.IsSubsite }).Count
        $subsites = ($sites | Where-Object { $_.IsSubsite }).Count
        Write-Host ""
        Write-Info "Site Collections: $siteCollections"
        Write-Info "Subsites: $subsites"
    }
    "Analyze" {
        if ([string]::IsNullOrEmpty($InputCsv) -or -not (Test-Path $InputCsv)) {
            Write-Err "Valid InputCsv is required for Analyze mode"
            exit 1
        }
        $results = @()
        $sites = Import-Csv $InputCsv
        Write-Info "Processing $($sites.Count) sites..."
        $processed = 0
        foreach ($site in $sites) {
            $processed++
            Write-Host ""
            Write-Info "[$processed / $($sites.Count)] $($site.SiteUrl)"
            $result = Invoke-SiteProcess -SiteUrl $site.SiteUrl -ProcessMode "Analyze"
            $results += $result
            $typeLabel = if ($result.SiteType) { "[$($result.SiteType)]" } else { "" }
            if ($result.NeedsModernization) {
                Write-Warn "  $typeLabel Needs modernization ($($result.ListsFound) lists, $($result.ItemCount) items)"
            }
            else {
                Write-Success "  $typeLabel Already modern"
            }
        }
        # Export results
        $outputFile = Join-Path $OutputPath "Analysis-$timestamp.csv"
        $results | ForEach-Object { [PSCustomObject]$_ } | Export-Csv -Path $outputFile -NoTypeInformation
        Write-Host ""
        Write-Success "Analysis complete"
        Write-Info "Output: $outputFile"
        # Summary
        $needsWork = ($results | Where-Object { $_.NeedsModernization }).Count
        $alreadyModern = ($results | Where-Object { $_.NeedsModernization -eq $false }).Count
        $errors = ($results | Where-Object { $_.Status -eq "Error" }).Count
        Write-Host ""
        Write-Info "Needs modernization: $needsWork"
        Write-Info "Already modern: $alreadyModern"
        if ($errors -gt 0) {
            Write-Warn "Errors: $errors"
        }
    }
    "Modernize" {
        if ([string]::IsNullOrEmpty($InputCsv) -or -not (Test-Path $InputCsv)) {
            Write-Err "Valid InputCsv is required for Modernize mode"
            exit 1
        }
        $sites = Import-Csv $InputCsv
        
        # Sort sites so parent sites are processed before subsites
        # This ensures QuickLaunch inheritance is properly disabled
        # Shorter URLs = parent sites, longer URLs = subsites
        $sites = $sites | Sort-Object { ($_.SiteUrl -split '/').Count }
        
        Write-Info "Modernizing $($sites.Count) sites (parents first, then subsites)..."
        Write-Host ""
        Write-Warn "This will modify all sites in the CSV."
        $confirm = Read-Host "Continue? (y/N)"
        if ($confirm -ne 'y' -and $confirm -ne 'Y') {
            Write-Info "Operation cancelled"
            exit 0
        }
        $processed = 0
        foreach ($site in $sites) {
            $processed++
            Write-Host ""
            Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor White
            Write-Info "[$processed / $($sites.Count)] $($site.SiteUrl)"
            Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor White
            $result = Invoke-SiteProcess -SiteUrl $site.SiteUrl -ProcessMode "Modernize"
            $results += $result
            if ($result.Status -eq "Modernized") {
                Write-Success "  Modernization complete"
            }
            elseif ($result.Status -eq "Error") {
                Write-Err "  Failed: $($result.Error)"
            }
        }
        # Export results
        $outputFile = Join-Path $OutputPath "Modernization-$timestamp.csv"
        $results | ForEach-Object { [PSCustomObject]$_ } | Export-Csv -Path $outputFile -NoTypeInformation
        Write-Host ""
        Write-Success "Batch modernization complete"
        Write-Info "Output: $outputFile"
        # Summary
        $success = ($results | Where-Object { $_.Status -eq "Modernized" }).Count
        $errors = ($results | Where-Object { $_.Status -eq "Error" }).Count
        Write-Host ""
        Write-Info "Successfully modernized: $success"
        if ($errors -gt 0) {
            Write-Warn "Errors: $errors"
        }
    }
}
Write-Host ""
Write-Info "Done."
