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

    [Parameter(Mandatory)]
    [ValidateSet('Seed', 'Read', 'QueryLogs')]
    [string] $Operation,

    [ValidateSet('Allowed', 'Denied', 'Any')]
    [string] $ExpectedAccess = 'Any',

    [ValidateRange(1, 30)]
    [int] $MaxAttempts = 1,

    [ValidateRange(5, 300)]
    [int] $RetrySeconds = 30,

    [ValidatePattern('^[a-z0-9-]*$')]
    [string] $ResultLabel = ''
)

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

Assert-WorkshopSubscription -SubscriptionId $SubscriptionId | Out-Null
$runtime = Get-WorkshopRuntime
$outputs = Get-WorkshopTerraformOutputs -SubscriptionId $SubscriptionId
$work = Get-WorkshopWorkDirectory

$resourceGroup = [string]$outputs.resource_group_name
$vmName = [string]$outputs.test_vm_name
$storageName = [string]$outputs.storage_account_name
$vaultName = [string]$outputs.key_vault_name
$identityClientId = [string]$outputs.test_identity_client_id
$workspaceCustomerId = [string]$outputs.workspace_customer_id
$manifest = $outputs.workshop_manifest

function New-DataPlaneScript {
    param([switch] $Seed)

    $seedFlag = if ($Seed) { '1' } else { '0' }
    return @"
set -u
CLIENT_ID='$identityClientId'
STORAGE_NAME='$storageName'
VAULT_NAME='$vaultName'
SEED='$seedFlag'
CONTAINER='perimeter-proof'
BLOB='proof.txt'
EXPECTED='Workshopz-NSP-proof-v1'

get_token() {
  resource="`$1"
  encoded=`$(python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "`$resource")
  curl -sS --connect-timeout 10 --max-time 30 -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=`$encoded&client_id=`$CLIENT_ID" | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])'
}

storage_token=`$(get_token 'https://storage.azure.com/')
vault_token=`$(get_token 'https://vault.azure.net')
xms_date=`$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S GMT')
container_code=0
blob_put_code=0
secret_put_code=0

if [ "`$SEED" = '1' ]; then
  container_code=`$(curl -sS --connect-timeout 10 --max-time 60 -o /tmp/container-body -w '%{http_code}' -X PUT \
    -H "Authorization: Bearer `$storage_token" -H 'x-ms-version: 2023-11-03' -H "x-ms-date: `$xms_date" -H 'Content-Length: 0' \
    "https://`$STORAGE_NAME.blob.core.windows.net/`$CONTAINER?restype=container")
  blob_put_code=`$(printf '%s' "`$EXPECTED" | curl -sS --connect-timeout 10 --max-time 60 -o /tmp/blob-put-body -w '%{http_code}' -X PUT \
    -H "Authorization: Bearer `$storage_token" -H 'x-ms-version: 2023-11-03' -H "x-ms-date: `$xms_date" -H 'x-ms-blob-type: BlockBlob' \
    --data-binary @- "https://`$STORAGE_NAME.blob.core.windows.net/`$CONTAINER/`$BLOB")
  secret_put_code=`$(printf '%s' '{"value":"Workshopz-NSP-proof-v1"}' | curl -sS --connect-timeout 10 --max-time 60 -o /tmp/secret-put-body -w '%{http_code}' -X PUT \
    -H "Authorization: Bearer `$vault_token" -H 'Content-Type: application/json' --data-binary @- \
    "https://`$VAULT_NAME.vault.azure.net/secrets/perimeter-proof?api-version=7.4")
fi

storage_code=`$(curl -sS --connect-timeout 10 --max-time 60 -o /tmp/blob-body -w '%{http_code}' \
  -H "Authorization: Bearer `$storage_token" -H 'x-ms-version: 2023-11-03' -H "x-ms-date: `$xms_date" \
  "https://`$STORAGE_NAME.blob.core.windows.net/`$CONTAINER/`$BLOB")
vault_code=`$(curl -sS --connect-timeout 10 --max-time 60 -o /tmp/vault-body -w '%{http_code}' \
  -H "Authorization: Bearer `$vault_token" \
  "https://`$VAULT_NAME.vault.azure.net/secrets/perimeter-proof?api-version=7.4")

storage_match=0
vault_match=0
if [ "`$storage_code" = '200' ] && [ "`$(cat /tmp/blob-body)" = "`$EXPECTED" ]; then storage_match=1; fi
if [ "`$vault_code" = '200' ]; then
  vault_match=`$(python3 -c 'import json,sys; print(1 if json.load(open("/tmp/vault-body")).get("value") == "Workshopz-NSP-proof-v1" else 0)' 2>/dev/null || echo 0)
fi

printf 'RESULT_JSON={"operation":"%s","containerHttp":%s,"blobPutHttp":%s,"secretPutHttp":%s,"storageHttp":%s,"keyVaultHttp":%s,"storageContentMatch":%s,"keyVaultValueMatch":%s}\n' \
  "$(if ($Seed) { 'Seed' } else { 'Read' })" "`$container_code" "`$blob_put_code" "`$secret_put_code" "`$storage_code" "`$vault_code" "`$storage_match" "`$vault_match"
"@
}

function New-LogQueryScript {
    $storageId = [string]$manifest.storage_id
    $vaultId = [string]$manifest.key_vault_id
    $kql = @"
NSPAccessLogs
| where TimeGenerated > ago(8h)
| where ServiceResourceId in~ ('$storageId', '$vaultId')
| extend EventCount = coalesce(tolong(Count), tolong(1))
| summarize Events=sum(EventCount) by Category, ServiceResourceId
| order by Category asc
"@
    $payload = @{ query = $kql } | ConvertTo-Json -Compress
    $payloadB64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($payload))

    return @"
set -u
CLIENT_ID='$identityClientId'
WORKSPACE_ID='$workspaceCustomerId'
PAYLOAD_B64='$payloadB64'
encoded=`$(python3 -c 'import urllib.parse; print(urllib.parse.quote("https://api.loganalytics.io", safe=""))')
token=`$(curl -sS --connect-timeout 10 --max-time 30 -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=`$encoded&client_id=`$CLIENT_ID" | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')
printf '%s' "`$PAYLOAD_B64" | base64 -d > /tmp/query.json
query_code=`$(curl -sS --connect-timeout 10 --max-time 90 -o /tmp/query-body -w '%{http_code}' -X POST \
  -H "Authorization: Bearer `$token" -H 'Content-Type: application/json' --data-binary @/tmp/query.json \
  "https://api.loganalytics.azure.com/v1/workspaces/`$WORKSPACE_ID/query")
python3 - "`$query_code" <<'PY'
import json, sys
code = int(sys.argv[1])
result = {"operation":"QueryLogs", "queryHttp":code, "rows":[]}
if code == 200:
    data = json.load(open('/tmp/query-body'))
    for table in data.get('tables', []):
        cols = [c['name'] for c in table.get('columns', [])]
        for row in table.get('rows', []):
            item = dict(zip(cols, row))
            rid = str(item.get('ServiceResourceId', '')).lower()
            label = 'Storage' if '/storageaccounts/' in rid else ('KeyVault' if '/vaults/' in rid else 'Other')
            result['rows'].append({"category":item.get('Category'), "service":label, "events":int(item.get('Events') or 0)})
else:
    try:
        error = json.load(open('/tmp/query-body')).get('error', {})
        result['errorCode'] = str(error.get('code', 'Unknown'))[:80]
        result['errorMessage'] = str(error.get('message', ''))[:300]
    except Exception:
        result['errorCode'] = 'UnparseableResponse'
print('RESULT_JSON=' + json.dumps(result, separators=(',', ':')))
PY
"@
}

$remoteScript = switch ($Operation) {
    'Seed'      { New-DataPlaneScript -Seed }
    'Read'      { New-DataPlaneScript }
    'QueryLogs' { New-LogQueryScript }
}
$remoteScriptPath = Join-Path $work ("remote-{0}.sh" -f $Operation.ToLowerInvariant())
$remoteScript | Set-Content -LiteralPath $remoteScriptPath -Encoding utf8NoBOM

$lastResult = $null
for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
    $raw = Invoke-AzureCli -Arguments @(
        'vm', 'run-command', 'invoke',
        '--subscription', $SubscriptionId,
        '--resource-group', $resourceGroup,
        '--name', $vmName,
        '--command-id', 'RunShellScript',
        '--scripts', "@$remoteScriptPath",
        '--only-show-errors',
        '--output', 'json'
    ) -AllowFailure

    $rawPath = Join-Path $work ("raw-{0}-{1:yyyyMMddTHHmmss}.json" -f $Operation.ToLowerInvariant(), [DateTime]::UtcNow)
    $raw.Output | Set-Content -LiteralPath $rawPath -Encoding utf8NoBOM

    $messageText = ''
    if ($raw.ExitCode -eq 0) {
        try {
            $envelope = $raw.Output | ConvertFrom-Json -Depth 100
            $messageText = @($envelope.value.message) -join [Environment]::NewLine
        }
        catch {
            $messageText = ''
        }
    }

    if ($raw.ExitCode -eq 0 -and $messageText -match 'RESULT_JSON=(\{[^\r\n]+\})') {
        $lastResult = $Matches[1] | ConvertFrom-Json -Depth 100
        $passed = switch ($ExpectedAccess) {
            'Allowed' {
                if ($Operation -eq 'QueryLogs') { $lastResult.queryHttp -eq 200 }
                elseif ($Operation -eq 'Seed') {
                    $httpSummary = @(
                        [int]$lastResult.blobPutHttp,
                        [int]$lastResult.secretPutHttp,
                        [int]$lastResult.storageHttp,
                        [int]$lastResult.keyVaultHttp
                    ) -join ','
                    $httpSummary -eq '201,200,200,200' -and
                        [bool]$lastResult.storageContentMatch -and [bool]$lastResult.keyVaultValueMatch
                }
                else {
                    [int]$lastResult.storageHttp -eq 200 -and [int]$lastResult.keyVaultHttp -eq 200 -and
                    [bool]$lastResult.storageContentMatch -and [bool]$lastResult.keyVaultValueMatch
                }
            }
            'Denied' {
                $Operation -eq 'Read' -and [int]$lastResult.storageHttp -eq 403 -and [int]$lastResult.keyVaultHttp -eq 403
            }
            default { $true }
        }
        if ($passed) { break }
    }

    if ($attempt -lt $MaxAttempts) { Start-Sleep -Seconds $RetrySeconds }
}

if (-not $lastResult) { throw "The VM Run Command did not return a parseable test result for $Operation." }

$resultFileLabel = if ($ResultLabel) { $ResultLabel } else { $Operation.ToLowerInvariant() }
$resultPath = Join-Path $work ("result-{0}.json" -f $resultFileLabel)
Write-WorkshopJson -Path $resultPath -Value $lastResult

$safe = Protect-WorkshopText -Text ($lastResult | ConvertTo-Json -Depth 20) -Runtime $runtime -Outputs $outputs
Write-Output $safe

if ($ExpectedAccess -eq 'Allowed') {
    if ($Operation -eq 'QueryLogs' -and [int]$lastResult.queryHttp -ne 200) { throw 'The Log Analytics query was not allowed.' }
    if ($Operation -eq 'Seed' -and (-not ([int]$lastResult.blobPutHttp -eq 201 -and [int]$lastResult.secretPutHttp -eq 200 -and [int]$lastResult.storageHttp -eq 200 -and [int]$lastResult.keyVaultHttp -eq 200))) { throw 'The seed-and-read operation did not succeed.' }
    if ($Operation -eq 'Read' -and (-not ([int]$lastResult.storageHttp -eq 200 -and [int]$lastResult.keyVaultHttp -eq 200))) { throw 'The data-plane read was not allowed.' }
}
if ($ExpectedAccess -eq 'Denied' -and (-not ([int]$lastResult.storageHttp -eq 403 -and [int]$lastResult.keyVaultHttp -eq 403))) {
    throw 'Expected HTTP 403 from both Storage and Key Vault.'
}
