[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [ValidatePattern('^[0-9a-fA-F-]{36}$')]
    [string] $SubscriptionId,

    [ValidateSet('Preflight', 'Deploy', 'Observe', 'Enforce', 'Allow', 'Validate', 'All')]
    [string] $Stage = 'Preflight',

    [string] $Location = 'westeurope',

    [ValidateRange(300, 3600)]
    [int] $LogWaitSeconds = 2700
)

. (Join-Path $PSScriptRoot 'NetworkSecurityPerimeterWorkshop.Common.ps1')

$root = Get-WorkshopRoot
$work = Get-WorkshopWorkDirectory

function Write-StageHeading([string] $Text) {
    Write-Host "`n=== $Text ===" -ForegroundColor Cyan
}

function Invoke-PreflightStage {
    Write-StageHeading 'Stage 1 - Read-only Azure preflight and protected baseline'
    $account = Assert-WorkshopSubscription -SubscriptionId $SubscriptionId

    $terraformVersion = (& terraform version -json | ConvertFrom-Json).terraform_version
    if ($terraformVersion -ne '1.15.8') { throw "Terraform 1.15.8 is required; found $terraformVersion." }

    $azVersion = (Invoke-AzureCliJson -Arguments @('version')).'azure-cli'
    if ([version]$azVersion -lt [version]'2.75.0') { throw "Azure CLI 2.75.0 or later is required; found $azVersion." }
    if ($PSVersionTable.PSVersion -lt [version]'7.4.0') { throw "PowerShell 7.4 or later is required; found $($PSVersionTable.PSVersion)." }

    $principalId = Get-SignedInPrincipalObjectId
    $scope = "/subscriptions/$SubscriptionId"
    $assignments = Invoke-AzureCliJson -Arguments @(
        'role', 'assignment', 'list', '--scope', $scope, '--include-inherited',
        '--assignee-object-id', $principalId
    )
    $roles = @($assignments.roleDefinitionName)
    if ($roles -notcontains 'Owner') { throw 'Owner is required at the active subscription.' }
    if ($roles -notcontains 'User Access Administrator') { throw 'User Access Administrator is required at the active subscription.' }

    $requiredProviders = @(
        'Microsoft.Network', 'Microsoft.Storage', 'Microsoft.KeyVault',
        'Microsoft.OperationalInsights', 'Microsoft.Insights', 'Microsoft.Compute',
        'Microsoft.ManagedIdentity'
    )
    $missingProviders = [System.Collections.Generic.List[string]]::new()
    foreach ($provider in $requiredProviders) {
        $state = (Invoke-AzureCli -Arguments @('provider', 'show', '--namespace', $provider, '--query', 'registrationState', '--output', 'tsv')).Output.Trim()
        if ($state -ne 'Registered') { $missingProviders.Add($provider) }
    }
    if ($missingProviders.Count -gt 0) {
        throw "Required providers are not registered: $($missingProviders -join ', '). Preflight never registers providers."
    }

    $resources = Invoke-AzureCliJson -Arguments @('resource', 'list', '--subscription', $SubscriptionId)
    $collisions = @($resources | Where-Object {
        ([string]$_.name).StartsWith('awz-nsp', [StringComparison]::OrdinalIgnoreCase) -or
        ([string]$_.resourceGroup).StartsWith('rg-awz-nsp', [StringComparison]::OrdinalIgnoreCase) -or
        ([string]$_.name).StartsWith('nsp-awz-nsp', [StringComparison]::OrdinalIgnoreCase)
    })
    if ($collisions.Count -gt 0) { throw "Existing Workshopz NSP resources were found ($($collisions.Count)). Cleanup or choose a clean subscription." }

    $deletedVaults = Invoke-AzureCliJson -Arguments @('keyvault', 'list-deleted', '--subscription', $SubscriptionId)
    $deletedCollisionCount = @($deletedVaults | Where-Object { ([string]$_.name).StartsWith('kv-awz-nsp', [StringComparison]::OrdinalIgnoreCase) }).Count
    if ($deletedCollisionCount -gt 0) { throw 'A soft-deleted Workshopz Key Vault name exists. Purge the known prior lab before continuing.' }

    $sku = Invoke-AzureCliJson -Arguments @('vm', 'list-skus', '--location', $Location, '--size', 'Standard_B1s', '--all')
    $usableSku = @($sku | Where-Object { -not $_.restrictions -or $_.restrictions.Count -eq 0 }).Count -gt 0
    if (-not $usableSku) { throw "Standard_B1s is restricted or unavailable in $Location for this subscription." }

    $baseline = Get-WorkshopBaselineSnapshot -SubscriptionId $SubscriptionId
    Write-WorkshopJson -Path (Join-Path $work 'baseline.json') -Value $baseline

    $runtime = [ordered]@{
        createdAtUtc      = [DateTime]::UtcNow.ToString('o')
        subscriptionId    = $SubscriptionId
        tenantId          = [string]$account.tenantId
        principalObjectId = $principalId
        location          = $Location
        resourcePrefix    = 'awz-nsp'
    }
    Save-WorkshopRuntime -Runtime $runtime

    $init = Initialize-WorkshopTerraform -SubscriptionId $SubscriptionId
    $validate = Invoke-Terraform -SubscriptionId $SubscriptionId -Arguments @('validate', '-no-color')
    $format = & terraform "-chdir=$root" fmt -check -recursive 2>&1
    if ($LASTEXITCODE -ne 0) { throw "Terraform formatting check failed:`n$($format -join [Environment]::NewLine)" }

    $result = [ordered]@{
        stage                       = 'Preflight'
        azureMutations              = 0
        terraformVersion            = $terraformVersion
        azureCliVersion             = $azVersion
        powerShellVersion           = $PSVersionTable.PSVersion.ToString()
        owner                       = $true
        userAccessAdministrator     = $true
        requiredProvidersRegistered = $true
        namespaceCollisions         = 0
        softDeletedVaultCollisions  = 0
        standardB1sAvailable        = $true
        terraformValidated          = $true
        baselineCaptured            = $true
    }
    Write-WorkshopJson -Path (Join-Path $work 'result-preflight.json') -Value $result
    Write-Host 'PASS: read-only Azure preflight completed; zero Azure mutations.' -ForegroundColor Green
}

function Invoke-TerraformStage {
    param(
        [Parameter(Mandatory)] [string] $Name,
        [Parameter(Mandatory)] [string] $VariableFile,
        [string[]] $AllowedSubscriptionIds = @()
    )

    Assert-WorkshopSubscription -SubscriptionId $SubscriptionId | Out-Null
    $runtime = Get-WorkshopRuntime
    if ($runtime.subscriptionId -ne $SubscriptionId) { throw 'The runtime subscription does not match the requested subscription.' }
    Initialize-WorkshopTerraform -SubscriptionId $SubscriptionId | Out-Null

    $planPath = Join-Path $work "$Name.tfplan"
    $plan = Invoke-Terraform -SubscriptionId $SubscriptionId -AllowedSubscriptionIds $AllowedSubscriptionIds -Arguments @(
        'plan', '-no-color', "-var-file=$VariableFile", "-var=location=$Location", "-out=$planPath"
    )
    $plan.Output | Set-Content -LiteralPath (Join-Path $work "$Name-plan.txt") -Encoding utf8NoBOM
    $apply = Invoke-Terraform -SubscriptionId $SubscriptionId -AllowedSubscriptionIds $AllowedSubscriptionIds -Arguments @(
        'apply', '-no-color', '-auto-approve', $planPath
    )
    $apply.Output | Set-Content -LiteralPath (Join-Path $work "$Name-apply.txt") -Encoding utf8NoBOM
}

function Invoke-DeployStage {
    Write-StageHeading 'Stage 2 - Deploy Network Security Perimeter in Transition/Learning mode'
    Invoke-TerraformStage -Name 'learning' -VariableFile (Join-Path $root 'stages/01-learning.tfvars')
    $outputs = Get-WorkshopTerraformOutputs -SubscriptionId $SubscriptionId
    Write-WorkshopJson -Path (Join-Path $work 'terraform-outputs-private.json') -Value $outputs
    Write-Host 'PASS: Storage, Key Vault, and Log Analytics are associated in Learning (portal: Transition) mode.' -ForegroundColor Green
}

function Wait-ForLogCategory {
    param(
        [Parameter(Mandatory)] [string[]] $Categories,
        [Parameter(Mandatory)] [int] $TimeoutSeconds
    )
    $deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
    do {
        try {
            & (Join-Path $root 'Test-AzureNetworkSecurityPerimeter.ps1') -SubscriptionId $SubscriptionId -Operation QueryLogs -ExpectedAccess Allowed -MaxAttempts 1 -ResultLabel querylogs | Out-Host
            $result = Get-Content -Raw -LiteralPath (Join-Path $work 'result-querylogs.json') | ConvertFrom-Json -Depth 100
            $seen = @($result.rows.category | Sort-Object -Unique)
            if (@($Categories | Where-Object { $_ -notin $seen }).Count -eq 0) { return $result }
        }
        catch {
            Write-Host "Logs not ready: $($_.Exception.Message)" -ForegroundColor Yellow
        }
        if ([DateTime]::UtcNow -lt $deadline) { Start-Sleep -Seconds 120 }
    } while ([DateTime]::UtcNow -lt $deadline)
    throw "Timed out waiting for NSPAccessLogs categories: $($Categories -join ', ')"
}

function Invoke-ObserveStage {
    Write-StageHeading 'Stage 3 - Seed data and observe Transition-mode fallback'
    & (Join-Path $root 'Test-AzureNetworkSecurityPerimeter.ps1') -SubscriptionId $SubscriptionId -Operation Seed -ExpectedAccess Allowed -MaxAttempts 20 -RetrySeconds 30 -ResultLabel transition-seed | Out-Host
    Wait-ForLogCategory -Categories @('NspPublicInboundResourceRulesAllowed') -TimeoutSeconds $LogWaitSeconds | Out-Null
    Write-Host 'PASS: managed-identity data access succeeded and Transition-mode fallback was logged.' -ForegroundColor Green
}

function Invoke-EnforceStage {
    Write-StageHeading 'Stage 4 - Enforce the perimeter with no access rule'
    Invoke-TerraformStage -Name 'enforced-block' -VariableFile (Join-Path $root 'stages/02-enforced-block.tfvars')
    & (Join-Path $root 'Test-AzureNetworkSecurityPerimeter.ps1') -SubscriptionId $SubscriptionId -Operation Read -ExpectedAccess Denied -MaxAttempts 20 -RetrySeconds 30 -ResultLabel enforced-denied | Out-Host
    Write-Host 'PASS: Storage and Key Vault both returned HTTP 403.' -ForegroundColor Green
}

function Invoke-AllowStage {
    Write-StageHeading 'Stage 5 - Add explicit managed-identity subscription access'
    Invoke-TerraformStage -Name 'enforced-allow' -VariableFile (Join-Path $root 'stages/03-enforced-allow.tfvars') -AllowedSubscriptionIds @($SubscriptionId)
    & (Join-Path $root 'Test-AzureNetworkSecurityPerimeter.ps1') -SubscriptionId $SubscriptionId -Operation Read -ExpectedAccess Allowed -MaxAttempts 20 -RetrySeconds 30 -ResultLabel enforced-allowed | Out-Host
    Write-Host 'PASS: explicit perimeter access and service RBAC restored both reads.' -ForegroundColor Green
}

function Invoke-ValidateStage {
    Write-StageHeading 'Stage 6 - Validate logs, posture, and zero Terraform drift'
    Wait-ForLogCategory -Categories @(
        'NspPublicInboundResourceRulesAllowed',
        'NspPublicInboundPerimeterRulesDenied',
        'NspPublicInboundPerimeterRulesAllowed'
    ) -TimeoutSeconds $LogWaitSeconds | Out-Null

    & (Join-Path $root 'Get-AzureNetworkSecurityPerimeterEvidence.ps1') -SubscriptionId $SubscriptionId | Out-Host

    $plan = Invoke-Terraform -SubscriptionId $SubscriptionId -AllowedSubscriptionIds @($SubscriptionId) -AllowFailure -Arguments @(
        'plan', '-no-color', '-detailed-exitcode', "-var-file=$(Join-Path $root 'stages/03-enforced-allow.tfvars')", "-var=location=$Location"
    )
    $plan.Output | Set-Content -LiteralPath (Join-Path $work 'final-drift-plan.txt') -Encoding utf8NoBOM
    if ($plan.ExitCode -ne 0) { throw "Final Terraform plan must return 0; received $($plan.ExitCode)." }

    $result = [ordered]@{
        stage                    = 'Validate'
        transitionFallbackSeen  = $true
        enforcedDenialsSeen     = $true
        perimeterAllowsSeen     = $true
        terraformDetailedExit   = 0
        zeroDrift                = $true
    }
    Write-WorkshopJson -Path (Join-Path $work 'result-validate.json') -Value $result
    Write-Host 'PASS: evidence checks succeeded and Terraform reported zero drift.' -ForegroundColor Green
}

$stages = if ($Stage -eq 'All') { @('Preflight', 'Deploy', 'Observe', 'Enforce', 'Allow', 'Validate') } else { @($Stage) }
foreach ($currentStage in $stages) {
    switch ($currentStage) {
        'Preflight' { Invoke-PreflightStage }
        'Deploy'    { Invoke-DeployStage }
        'Observe'   { Invoke-ObserveStage }
        'Enforce'   { Invoke-EnforceStage }
        'Allow'     { Invoke-AllowStage }
        'Validate'  { Invoke-ValidateStage }
    }
}
