content/src/scripts/Invoke-PlatformBootstrap.ps1

# ---------------------------------------------------------------------------
# Invoke-PlatformBootstrap.ps1
# Axeon Core Engine — Data-Driven Azure Landing Zone Orchestrator
#
# Reads platform-spec.json, validates it, authenticates to Azure,
# and orchestrates modular Bicep deployments.
#
# Usage:
# ./Invoke-PlatformBootstrap.ps1 -ConfigPath "./platform-spec.json"
# ./Invoke-PlatformBootstrap.ps1 -ConfigPath "./platform-spec.json" -WhatIf
# ---------------------------------------------------------------------------
#Requires -Version 7.0
#Requires -Modules Az.Accounts, Az.Resources

[CmdletBinding(SupportsShouldProcess)]
param(
    [Parameter(Mandatory)]
    [ValidateScript({ Test-Path $_ -PathType Leaf })]
    [string]$ConfigPath,

    [string]$BicepRoot,

    [string]$SchemaPath
)

$ErrorActionPreference = "Stop"
$InformationPreference = "Continue"

# Dot-source the naming convention engine
. (Join-Path $PSScriptRoot "Resolve-ResourceName.ps1")

# ============================================================================
# HELPER FUNCTIONS
# ============================================================================

function Write-Banner {
    param([string]$Title)
    $sep = "=" * 65
    Write-Host ""
    Write-Host $sep -ForegroundColor DarkCyan
    Write-Host " $Title" -ForegroundColor Cyan
    Write-Host $sep -ForegroundColor DarkCyan
    Write-Host ""
}

function Write-StepHeader {
    param([int]$Step, [string]$Title)
    Write-Host "[$Step] $Title" -ForegroundColor Yellow
    Write-Host ("-" * 50) -ForegroundColor DarkGray
}

function Write-Success {
    param([string]$Message)
    Write-Host " [OK] $Message" -ForegroundColor Green
}

function Write-Detail {
    param([string]$Message)
    Write-Host " $Message" -ForegroundColor Gray
}

# ============================================================================
# STEP 1 — Load & Validate Configuration
# ============================================================================

function Read-PlatformSpec {
    param([string]$Path)

    Write-StepHeader -Step 1 -Title "Loading platform specification"
    Write-Detail "Config: $Path"

    $raw = Get-Content $Path -Raw
    $spec = $raw | ConvertFrom-Json -AsHashtable

    # Required top-level fields
    $requiredFields = @('platformId', 'environment', 'location', 'naming')
    foreach ($field in $requiredFields) {
        if (-not $spec.ContainsKey($field) -or (-not ($spec[$field] -is [hashtable]) -and [string]::IsNullOrWhiteSpace($spec[$field]))) {
            throw "platform-spec.json is missing required field: '$field'"
        }
    }

    # Defaults
    if (-not $spec.ContainsKey('testMode')) { $spec['testMode'] = $false }

    # Validate naming configuration
    Write-Detail "Validating naming convention..."
    $namingValid = Test-NamingConfig -NamingConfig $spec.naming
    if (-not $namingValid) {
        throw "Naming configuration is invalid. See warnings above."
    }
    Write-Success "Naming convention validated"

    # Resolve all resource names and attach to spec
    $spec['_resolvedNames'] = Resolve-AllResourceNames -NamingConfig $spec.naming -Index 0

    Write-Success "Configuration loaded"
    Write-Detail "Platform ID : $($spec.platformId)"
    Write-Detail "Environment : $($spec.environment)"
    Write-Detail "Location : $($spec.location)"
    Write-Detail "Test Mode : $($spec.testMode)"
    Write-Host ""

    # Show naming preview
    Show-NamingPreview -NamingConfig $spec.naming -Index 0

    return $spec
}

function Test-PlatformSpec {
    param(
        [hashtable]$Spec,
        [string]$SchemaFile
    )

    if (-not $SchemaFile -or -not (Test-Path $SchemaFile)) {
        Write-Detail "Schema validation skipped (no schema found)"
        return
    }

    Write-Detail "Validating against schema: $SchemaFile"

    # Basic structural validation — a full JSON Schema validator (e.g.
    # NJsonSchema) can be plugged in here. For now we validate key types.
    $schema = Get-Content $SchemaFile -Raw | ConvertFrom-Json -AsHashtable

    if ($schema.ContainsKey('required')) {
        foreach ($field in $schema.required) {
            if (-not $Spec.ContainsKey($field)) {
                throw "Schema validation failed: missing required field '$field'"
            }
        }
    }

    Write-Success "Schema validation passed"
    Write-Host ""
}

# ============================================================================
# STEP 2 — Azure Authentication
# ============================================================================

function Connect-AzureContext {
    param([hashtable]$Spec)

    Write-StepHeader -Step 2 -Title "Authenticating to Azure"

    # Check if already connected
    $context = Get-AzContext -ErrorAction SilentlyContinue
    if ($context) {
        Write-Success "Already authenticated as: $($context.Account.Id)"
        Write-Detail "Subscription : $($context.Subscription.Name) ($($context.Subscription.Id))"
        Write-Host ""
        return $context
    }

    # Try Managed Identity / Workload Identity (CI environments)
    try {
        Write-Detail "Attempting Workload Identity / Managed Identity..."
        Connect-AzAccount -Identity -ErrorAction Stop | Out-Null
        $context = Get-AzContext
        Write-Success "Authenticated via Managed Identity"
    }
    catch {
        # Fall back to interactive login for local development
        Write-Detail "Managed Identity not available. Falling back to interactive login..."
        Connect-AzAccount -ErrorAction Stop | Out-Null
        $context = Get-AzContext
        Write-Success "Authenticated interactively as: $($context.Account.Id)"
    }

    Write-Detail "Subscription : $($context.Subscription.Name) ($($context.Subscription.Id))"
    Write-Host ""
    return $context
}

# ============================================================================
# STEP 3 — Resource Group Provisioning
# ============================================================================

function Ensure-ResourceGroup {
    param(
        [hashtable]$Spec,
        [switch]$TestMode
    )

    Write-StepHeader -Step 3 -Title "Ensuring resource group"

    $rgName   = $Spec._resolvedNames.resourceGroup
    $location = $Spec.location

    Write-Detail "Resource Group : $rgName"
    Write-Detail "Location : $location"

    if ($TestMode) {
        Write-Detail "[Test Mode] Would create/ensure resource group '$rgName' in '$location'"
        Write-Host ""
        return $rgName
    }

    $existing = Get-AzResourceGroup -Name $rgName -ErrorAction SilentlyContinue
    if ($existing) {
        Write-Success "Resource group already exists"
    }
    else {
        if ($PSCmdlet.ShouldProcess($rgName, "Create resource group")) {
            New-AzResourceGroup -Name $rgName -Location $location -Force | Out-Null
            Write-Success "Resource group created"
        }
    }

    Write-Host ""
    return $rgName
}

# ============================================================================
# STEP 4 — Bicep Deployment
# ============================================================================

function Invoke-BicepDeployment {
    param(
        [hashtable]$Spec,
        [string]$ResourceGroupName,
        [string]$BicepFile,
        [switch]$TestMode
    )

    Write-StepHeader -Step 4 -Title "Deploying infrastructure"

    if (-not (Test-Path $BicepFile)) {
        Write-Detail "Bicep entry point not found at: $BicepFile"
        Write-Detail "Skipping Bicep deployment (no templates found)"
        Write-Host ""
        return
    }

    Write-Detail "Template : $BicepFile"
    Write-Detail "Target : $ResourceGroupName"

    $deploymentName = "axeon-$($Spec.platformId)-$(Get-Date -Format 'yyyyMMdd-HHmmss')"

    # Build Bicep parameter object from platform-spec + resolved names
    $resolvedNames = $Spec._resolvedNames
    $parameters = @{
        platformId   = $Spec.platformId
        environment  = $Spec.environment
        location     = $Spec.location
        vnetName     = $resolvedNames.virtualNetwork
    }

    # Add networking parameters if present
    if ($Spec.ContainsKey('networking')) {
        $net = $Spec.networking
        if ($net.ContainsKey('addressPrefix'))  { $parameters['addressPrefix']  = $net.addressPrefix }
    }

    # Convert to Bicep-compatible parameter hashtable
    $templateParams = @{}
    foreach ($key in $parameters.Keys) {
        $templateParams[$key] = @{ value = $parameters[$key] }
    }

    if ($TestMode) {
        # Validation-only mode (What-If)
        Write-Detail "[Test Mode] Running deployment validation (What-If)..."

        $result = Test-AzResourceGroupDeployment `
            -ResourceGroupName $ResourceGroupName `
            -TemplateFile $BicepFile `
            -TemplateParameterObject $parameters `
            -ErrorAction Stop

        if ($result) {
            Write-Host " [!] Validation issues:" -ForegroundColor DarkYellow
            foreach ($err in $result) {
                Write-Host " - $($err.Message)" -ForegroundColor DarkYellow
            }
        }
        else {
            Write-Success "Deployment validation passed — no errors detected"
        }

        # Also run What-If for a detailed change preview
        Write-Detail ""
        Write-Detail "What-If deployment preview:"
        $whatIf = Get-AzResourceGroupDeploymentWhatIfResult `
            -ResourceGroupName $ResourceGroupName `
            -TemplateFile $BicepFile `
            -TemplateParameterObject $parameters `
            -ErrorAction Stop

        foreach ($change in $whatIf.Changes) {
            $symbol = switch ($change.ChangeType) {
                'Create'  { '+' }
                'Delete'  { '-' }
                'Modify'  { '~' }
                'NoChange'{ '=' }
                default   { '?' }
            }
            Write-Detail " [$symbol] $($change.ChangeType): $($change.ResourceId)"
        }

        Write-Host ""
        return
    }

    # Full deployment
    if ($PSCmdlet.ShouldProcess($ResourceGroupName, "Deploy Bicep template")) {
        Write-Detail "Starting deployment: $deploymentName"

        $deployment = New-AzResourceGroupDeployment `
            -Name $deploymentName `
            -ResourceGroupName $ResourceGroupName `
            -TemplateFile $BicepFile `
            -TemplateParameterObject $parameters `
            -ErrorAction Stop

        Write-Success "Deployment completed"
        Write-Detail "Provisioning State : $($deployment.ProvisioningState)"
        Write-Detail "Deployment Name : $($deployment.DeploymentName)"

        if ($deployment.Outputs) {
            Write-Host ""
            Write-Detail "Outputs:"
            foreach ($key in $deployment.Outputs.Keys) {
                Write-Detail " $key = $($deployment.Outputs[$key].Value)"
            }
        }
    }

    Write-Host ""
}

# ============================================================================
# STEP 5 — Summary
# ============================================================================

function Write-DeploymentSummary {
    param(
        [hashtable]$Spec,
        [string]$ResourceGroupName,
        [switch]$TestMode
    )

    Write-StepHeader -Step 5 -Title "Deployment summary"

    $mode = if ($TestMode) { "VALIDATION ONLY (Test Mode)" } else { "DEPLOYED" }

    Write-Host ""
    Write-Host " Platform ID : $($Spec.platformId)"  -ForegroundColor White
    Write-Host " Environment : $($Spec.environment)"  -ForegroundColor White
    Write-Host " Location : $($Spec.location)"     -ForegroundColor White
    Write-Host " Resource Group : $ResourceGroupName"     -ForegroundColor White
    Write-Host " Status : $mode"                  -ForegroundColor $(if ($TestMode) { 'Yellow' } else { 'Green' })
    Write-Host ""

    if ($TestMode) {
        Write-Host " To deploy for real, set testMode to false in platform-spec.json" -ForegroundColor DarkGray
    }
}

# ============================================================================
# MAIN EXECUTION
# ============================================================================

$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()

Write-Banner "Axeon Platform Bootstrap Engine"

# Resolve paths
$scriptDir = $PSScriptRoot
if (-not $BicepRoot)  { $BicepRoot  = Join-Path $scriptDir ".." "bicep" }
if (-not $SchemaPath) { $SchemaPath = Join-Path $scriptDir ".." "schemas" "platform-spec.schema.json" }

$bicepEntry = Join-Path $BicepRoot "main.bicep"

try {
    # Step 1 — Load & validate config
    $spec = Read-PlatformSpec -Path $ConfigPath
    Test-PlatformSpec -Spec $spec -SchemaFile $SchemaPath

    $isTestMode = [bool]$spec.testMode

    # Step 2 — Authenticate
    Connect-AzureContext -Spec $spec

    # Step 3 — Ensure resource group
    $rgName = Ensure-ResourceGroup -Spec $spec -TestMode:$isTestMode

    # Step 4 — Deploy Bicep
    Invoke-BicepDeployment `
        -Spec $spec `
        -ResourceGroupName $rgName `
        -BicepFile $bicepEntry `
        -TestMode:$isTestMode

    # Step 5 — Summary
    Write-DeploymentSummary -Spec $spec -ResourceGroupName $rgName -TestMode:$isTestMode

    $stopwatch.Stop()
    Write-Host " Completed in $([math]::Round($stopwatch.Elapsed.TotalSeconds, 1))s" -ForegroundColor DarkGray
    Write-Host ""
}
catch {
    $stopwatch.Stop()
    Write-Host ""
    Write-Host " BOOTSTRAP FAILED" -ForegroundColor Red
    Write-Host " $($_.Exception.Message)" -ForegroundColor Red
    Write-Host ""
    throw
}