<#
.SYNOPSIS
    Exports Teams Phone reference data (locations and policies) to CSV files for the Teams Phone CLI.

.DESCRIPTION
    This script connects to Microsoft Teams using service principal authentication and exports:
    - Emergency locations (civic addresses and locations) to locations.csv
    - Voice policies (calling, voicemail, emergency, etc.) to policies.csv
    - Export metadata to export-metadata.json

    The exported CSV files are used by the Teams Phone CLI for validation, display, and tab completion
    since Microsoft Graph API does not provide endpoints for these resources.

.PARAMETER TenantId
    The Azure AD tenant ID (GUID).

.PARAMETER ApplicationId
    The Azure AD application (client) ID for the service principal.

.PARAMETER CertificateThumbprint
    The thumbprint of the certificate in the Windows certificate store.
    Use this for Windows environments where the cert is installed in the user's cert store.

.PARAMETER CertificatePath
    The path to a .pfx certificate file.
    Use this for cross-platform environments or when the cert is stored as a file.

.PARAMETER CertificatePassword
    The password for the .pfx certificate file (SecureString).
    Required when using -CertificatePath.

.PARAMETER OutputPath
    The directory where CSV files will be written.
    Default: ~/.teams-phone/cache/

.EXAMPLE
    # Windows with certificate in cert store
    ./Export-TeamsPhoneData.ps1 `
        -TenantId "12345678-1234-1234-1234-123456789012" `
        -ApplicationId "87654321-4321-4321-4321-210987654321" `
        -CertificateThumbprint "ABC123DEF456789..."

.EXAMPLE
    # Cross-platform with .pfx file
    $password = ConvertTo-SecureString "YourPassword" -AsPlainText -Force
    ./Export-TeamsPhoneData.ps1 `
        -TenantId "12345678-1234-1234-1234-123456789012" `
        -ApplicationId "87654321-4321-4321-4321-210987654321" `
        -CertificatePath "./certs/TeamsPhoneCLI.pfx" `
        -CertificatePassword $password

.NOTES
    Requires: MicrosoftTeams PowerShell module (Install-Module -Name MicrosoftTeams)
    Requires: Teams Administrator role assigned to the service principal
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [ValidatePattern('^[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}$')]
    [string]$TenantId,

    [Parameter(Mandatory = $true)]
    [ValidatePattern('^[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}$')]
    [string]$ApplicationId,

    [Parameter(Mandatory = $false)]
    [string]$CertificateThumbprint,

    [Parameter(Mandatory = $false)]
    [string]$CertificatePath,

    [Parameter(Mandatory = $false)]
    [SecureString]$CertificatePassword,

    [Parameter(Mandatory = $false)]
    [string]$OutputPath = (Join-Path (Join-Path $HOME ".teams-phone") "cache")
)

# Strict mode for better error handling
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

#region Helper Functions

function Write-Log {
    param(
        [string]$Message,
        [ValidateSet("INFO", "WARN", "ERROR", "SUCCESS")]
        [string]$Level = "INFO"
    )

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $color = switch ($Level) {
        "INFO"    { "Cyan" }
        "WARN"    { "Yellow" }
        "ERROR"   { "Red" }
        "SUCCESS" { "Green" }
    }

    Write-Host "[$timestamp] " -NoNewline
    Write-Host "[$Level] " -ForegroundColor $color -NoNewline
    Write-Host $Message
}

function Test-MicrosoftTeamsModule {
    if (-not (Get-Module -ListAvailable -Name MicrosoftTeams)) {
        Write-Log "MicrosoftTeams module not found. Install it with: Install-Module -Name MicrosoftTeams -Force" -Level ERROR
        throw "MicrosoftTeams module is not installed."
    }
    Write-Log "MicrosoftTeams module found" -Level INFO
}

function Connect-TeamsWithCertificate {
    param(
        [string]$TenantId,
        [string]$ApplicationId,
        [string]$CertificateThumbprint,
        [string]$CertificatePath,
        [SecureString]$CertificatePassword
    )

    Write-Log "Connecting to Microsoft Teams..." -Level INFO

    try {
        if ($CertificateThumbprint) {
            # Windows certificate store authentication
            Write-Log "Using certificate thumbprint authentication" -Level INFO
            Connect-MicrosoftTeams `
                -TenantId $TenantId `
                -ApplicationId $ApplicationId `
                -CertificateThumbprint $CertificateThumbprint | Out-Null
        }
        elseif ($CertificatePath) {
            # Cross-platform file-based certificate authentication
            Write-Log "Using certificate file authentication" -Level INFO

            if (-not (Test-Path $CertificatePath)) {
                throw "Certificate file not found: $CertificatePath"
            }

            if ($CertificatePassword) {
                $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new(
                    $CertificatePath,
                    $CertificatePassword
                )
            }
            else {
                $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($CertificatePath)
            }

            Connect-MicrosoftTeams `
                -TenantId $TenantId `
                -ApplicationId $ApplicationId `
                -Certificate $cert | Out-Null
        }
        else {
            throw "Either -CertificateThumbprint or -CertificatePath must be specified"
        }

        Write-Log "Connected to Microsoft Teams successfully" -Level SUCCESS
    }
    catch {
        Write-Log "Failed to connect to Microsoft Teams: $($_.Exception.Message)" -Level ERROR
        throw
    }
}

function Export-Locations {
    param([string]$OutputPath)

    Write-Log "Exporting emergency locations..." -Level INFO

    try {
        # Get all civic addresses first (to get default location info)
        Write-Log "Fetching civic addresses..." -Level INFO
        $civicAddresses = Get-CsOnlineLisCivicAddress -ErrorAction Stop
        Write-Log "Found $($civicAddresses.Count) civic addresses" -Level INFO

        # Create a hashtable for quick lookup of default locations
        $defaultLocations = @{}
        foreach ($addr in $civicAddresses) {
            if ($addr.DefaultLocationId) {
                $defaultLocations[$addr.DefaultLocationId] = $true
            }
        }

        # Get all locations
        Write-Log "Fetching locations..." -Level INFO
        $locations = Get-CsOnlineLisLocation -ErrorAction Stop
        Write-Log "Found $($locations.Count) locations" -Level INFO

        if ($locations.Count -eq 0) {
            Write-Log "No emergency locations found in tenant" -Level WARN
            return 0
        }

        # Build location records with full address info
        $locationRecords = foreach ($loc in $locations) {
            # Build the full address string
            $addressParts = @()
            if ($loc.HouseNumber) { $addressParts += $loc.HouseNumber }
            if ($loc.HouseNumberSuffix) { $addressParts += $loc.HouseNumberSuffix }
            if ($loc.PreDirectional) { $addressParts += $loc.PreDirectional }
            if ($loc.StreetName) { $addressParts += $loc.StreetName }
            if ($loc.StreetSuffix) { $addressParts += $loc.StreetSuffix }
            if ($loc.PostDirectional) { $addressParts += $loc.PostDirectional }

            $fullAddress = ($addressParts -join " ").Trim()

            # Use Location field for description, fallback to Description
            $description = if ($loc.Location) { $loc.Location } else { $loc.Description }

            [PSCustomObject]@{
                locationId       = $loc.LocationId
                civicAddressId   = $loc.CivicAddressId
                description      = $description
                companyName      = $loc.CompanyName
                address          = $fullAddress
                city             = $loc.City
                stateOrProvince  = $loc.StateOrProvince
                postalCode       = $loc.PostalCode
                countryOrRegion  = $loc.CountryOrRegion
                isDefault        = $defaultLocations.ContainsKey($loc.LocationId)
            }
        }

        # Export to CSV
        $csvPath = Join-Path $OutputPath "locations.csv"
        $locationRecords | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
        Write-Log "Exported $($locationRecords.Count) locations to $csvPath" -Level SUCCESS

        return $locationRecords.Count
    }
    catch {
        Write-Log "Failed to export locations: $($_.Exception.Message)" -Level ERROR
        throw
    }
}

function Export-Policies {
    param([string]$OutputPath)

    Write-Log "Exporting voice policies..." -Level INFO

    $allPolicies = @()

    # Define policy types to export with their cmdlets and Graph API type names
    $policyTypes = @(
        @{ Cmdlet = "Get-CsTeamsCallingPolicy"; GraphType = "TeamsCallingPolicy" }
        @{ Cmdlet = "Get-CsOnlineVoicemailPolicy"; GraphType = "TeamsVoicemailPolicy" }
        @{ Cmdlet = "Get-CsCallingLineIdentity"; GraphType = "CallingLineIdentity" }
        @{ Cmdlet = "Get-CsTenantDialPlan"; GraphType = "TenantDialPlan" }
        @{ Cmdlet = "Get-CsOnlineVoiceRoutingPolicy"; GraphType = "OnlineVoiceRoutingPolicy" }
        @{ Cmdlet = "Get-CsTeamsEmergencyCallingPolicy"; GraphType = "TeamsEmergencyCallingPolicy" }
        @{ Cmdlet = "Get-CsTeamsEmergencyCallRoutingPolicy"; GraphType = "TeamsEmergencyCallRoutingPolicy" }
    )

    foreach ($policyType in $policyTypes) {
        Write-Log "Fetching $($policyType.GraphType) policies..." -Level INFO

        try {
            # Wrap in @() to ensure array even for single results
            $policies = @(Invoke-Expression $policyType.Cmdlet -ErrorAction Stop)

            if ($policies.Count -gt 0) {
                foreach ($policy in $policies) {
                    $policyName = $policy.Identity

                    # Handle "Global" vs "Tag:PolicyName" format
                    if ($policyName -like "Tag:*") {
                        $policyName = $policyName.Substring(4)
                    }

                    $allPolicies += [PSCustomObject]@{
                        policyType  = $policyType.GraphType
                        policyName  = $policyName
                        policyId    = $policyName  # For most policies, name = id
                        description = if ($policy.Description) { $policy.Description } else { "" }
                        isGlobal    = ($policyName -eq "Global")
                    }
                }
                Write-Log "Found $($policies.Count) $($policyType.GraphType) policies" -Level INFO
            }
            else {
                Write-Log "No $($policyType.GraphType) policies found" -Level WARN
            }
        }
        catch {
            Write-Log "Failed to fetch $($policyType.GraphType): $($_.Exception.Message)" -Level WARN
            # Continue with other policy types
        }
    }

    if ($allPolicies.Count -eq 0) {
        Write-Log "No policies found in tenant" -Level WARN
        return 0
    }

    # Export to CSV
    $csvPath = Join-Path $OutputPath "policies.csv"
    $allPolicies | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
    Write-Log "Exported $($allPolicies.Count) policies to $csvPath" -Level SUCCESS

    return $allPolicies.Count
}

function Export-Metadata {
    param(
        [string]$OutputPath,
        [string]$TenantId,
        [int]$LocationCount,
        [int]$PolicyCount
    )

    Write-Log "Writing export metadata..." -Level INFO

    # Try to get tenant display name
    $tenantName = $TenantId
    try {
        $tenantInfo = Get-CsTenant -ErrorAction SilentlyContinue
        if ($tenantInfo -and $tenantInfo.DisplayName) {
            $tenantName = $tenantInfo.DisplayName
        }
    }
    catch {
        # Ignore - use tenant ID as fallback
    }

    $metadata = @{
        exportedAt    = (Get-Date -Format "o")
        exportedBy    = "service-principal"
        tenantId      = $TenantId
        tenantName    = $tenantName
        scriptVersion = "1.0.0"
        counts        = @{
            locations = $LocationCount
            policies  = $PolicyCount
        }
    }

    $jsonPath = Join-Path $OutputPath "export-metadata.json"
    $metadata | ConvertTo-Json -Depth 3 | Set-Content -Path $jsonPath -Encoding UTF8
    Write-Log "Metadata written to $jsonPath" -Level SUCCESS
}

#endregion

#region Main Execution

try {
    Write-Log "Teams Phone Data Export Script v1.0.0" -Level INFO
    Write-Log "======================================" -Level INFO

    # Validate parameters
    if (-not $CertificateThumbprint -and -not $CertificatePath) {
        throw "Either -CertificateThumbprint or -CertificatePath must be specified"
    }

    if ($CertificatePath -and -not $CertificatePassword) {
        Write-Log "No password provided for certificate file. Attempting to load without password..." -Level WARN
    }

    # Check for MicrosoftTeams module
    Test-MicrosoftTeamsModule

    # Import the module
    Import-Module MicrosoftTeams -ErrorAction Stop

    # Create output directory if it doesn't exist
    if (-not (Test-Path $OutputPath)) {
        Write-Log "Creating output directory: $OutputPath" -Level INFO
        New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
    }

    # Connect to Teams
    Connect-TeamsWithCertificate `
        -TenantId $TenantId `
        -ApplicationId $ApplicationId `
        -CertificateThumbprint $CertificateThumbprint `
        -CertificatePath $CertificatePath `
        -CertificatePassword $CertificatePassword

    # Export data
    $locationCount = Export-Locations -OutputPath $OutputPath
    $policyCount = Export-Policies -OutputPath $OutputPath

    # Write metadata
    Export-Metadata `
        -OutputPath $OutputPath `
        -TenantId $TenantId `
        -LocationCount $locationCount `
        -PolicyCount $policyCount

    # Disconnect
    Write-Log "Disconnecting from Microsoft Teams..." -Level INFO
    Disconnect-MicrosoftTeams | Out-Null

    Write-Log "======================================" -Level INFO
    Write-Log "Export completed successfully!" -Level SUCCESS
    Write-Log "Output directory: $OutputPath" -Level INFO
    Write-Log "  - locations.csv: $locationCount locations" -Level INFO
    Write-Log "  - policies.csv: $policyCount policies" -Level INFO
    Write-Log "  - export-metadata.json" -Level INFO
}
catch {
    Write-Log "Export failed: $($_.Exception.Message)" -Level ERROR
    Write-Log $_.ScriptStackTrace -Level ERROR

    # Try to disconnect on error
    try { Disconnect-MicrosoftTeams -ErrorAction SilentlyContinue } catch {}

    exit 1
}

#endregion
