[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [ValidateRange(1, 65535)]
    [int]$ExpectedTcpPort,

    [Parameter(Mandatory = $true)]
    [ValidateLength(1, 128)]
    [string]$ExpectedTestWindowTitle
)

$ErrorActionPreference = 'Stop'

function Write-SafeResult {
    param(
        [Parameter(Mandatory = $true)]
        [object]$Value,

        [Parameter(Mandatory = $true)]
        [int]$ExitCode
    )

    $Value | ConvertTo-Json -Depth 6
    exit $ExitCode
}

function Test-ProgramWindowTitle {
    param([AllowEmptyString()][string]$Title)

    return $Title -match '(?i)(^|[^A-Z0-9])(PGM|PROGRAM)([^A-Z0-9]|$)'
}

try {
    $expectedTitle = $ExpectedTestWindowTitle.Trim()
    if (-not $expectedTitle.StartsWith('Tornado2', [StringComparison]::OrdinalIgnoreCase) -or
        $expectedTitle -notmatch '(?i)(^|[^A-Z0-9])TEST([^A-Z0-9]|$)' -or
        (Test-ProgramWindowTitle $expectedTitle)) {
        Write-SafeResult -ExitCode 64 -Value ([ordered]@{
            command = 'test-isolation-probe'
            ready = $false
            errorCode = 'invalid-test-window-title'
            connectRequestIssued = $false
            comActivationAttempted = $false
            ktapConnectAttempted = $false
        })
    }

    $processInspectionAvailable = $true
    $tornadoProcesses = @()
    try {
        foreach ($process in Get-Process -ErrorAction Stop) {
            try {
                if (-not $process.ProcessName.StartsWith(
                    'Tornado2',
                    [StringComparison]::OrdinalIgnoreCase)) {
                    continue
                }

                $title = $process.MainWindowTitle
                $tornadoProcesses += [pscustomobject]@{
                    Id = $process.Id
                    IsExpectedTest = [string]::Equals(
                        $title,
                        $expectedTitle,
                        [StringComparison]::OrdinalIgnoreCase)
                    IsProgram = Test-ProgramWindowTitle $title
                }
            }
            catch {
                $processInspectionAvailable = $false
            }
            finally {
                $process.Dispose()
            }
        }
    }
    catch {
        $processInspectionAvailable = $false
        $tornadoProcesses = @()
    }

    $eligibleProcesses = @($tornadoProcesses | Where-Object IsExpectedTest)
    $programProcesses = @($tornadoProcesses | Where-Object IsProgram)

    $registryAvailable = $false
    $configuredPortMatches = $false
    $networkAddressIsLoopback = $false
    $networkAutoStart = $false
    try {
        $controlKey = Get-Item -LiteralPath (
            'Registry::HKEY_CURRENT_USER\Software\Visual Research Inc\Tornado2\Option\Control')
        $configuredPort = [int]$controlKey.GetValue('Network TCP Port', 0)
        $configuredAddress = [string]$controlKey.GetValue('Network Address', '')
        $networkAutoStart = [int]$controlKey.GetValue('Network Auto Start', 0) -ne 0
        $parsedAddress = $null
        $addressParsed = [Net.IPAddress]::TryParse($configuredAddress, [ref]$parsedAddress)
        $networkAddressIsLoopback = $addressParsed -and
            [Net.IPAddress]::IsLoopback($parsedAddress)
        $configuredPortMatches = $configuredPort -eq $ExpectedTcpPort
        $registryAvailable = $true
    }
    catch {
        $registryAvailable = $false
    }

    $listenerInspectionAvailable = $true
    $listeners = @()
    try {
        $listeners = @(Get-NetTCPConnection `
            -State Listen `
            -LocalPort $ExpectedTcpPort `
            -ErrorAction Stop)
    }
    catch [Microsoft.PowerShell.Cmdletization.Cim.CimJobException] {
        # Get-NetTCPConnection uses this exception for an empty query on some Windows builds.
        $listeners = @()
    }
    catch {
        $listenerInspectionAvailable = $false
        $listeners = @()
    }

    $listenerAddressesAreLoopback = $listeners.Count -gt 0
    foreach ($listener in $listeners) {
        $parsedListenerAddress = $null
        $listenerAddressParsed = [Net.IPAddress]::TryParse(
            [string]$listener.LocalAddress,
            [ref]$parsedListenerAddress)
        if (-not $listenerAddressParsed -or
            -not [Net.IPAddress]::IsLoopback($parsedListenerAddress)) {
            $listenerAddressesAreLoopback = $false
            break
        }
    }

    $eligibleProcessIds = @($eligibleProcesses | ForEach-Object Id)
    $listenerOwnerIds = @($listeners | ForEach-Object OwningProcess | Sort-Object -Unique)
    $listenerOwnedOnlyByTest = $listeners.Count -gt 0 -and
        $listenerOwnerIds.Count -gt 0 -and
        @($listenerOwnerIds | Where-Object { $_ -notin $eligibleProcessIds }).Count -eq 0

    $reasons = [Collections.Generic.List[string]]::new()
    if (-not $processInspectionAvailable) {
        $reasons.Add('process-inspection-unavailable')
    }
    if ($tornadoProcesses.Count -ne 1) {
        $reasons.Add('tornado-process-count-not-one')
    }
    if ($eligibleProcesses.Count -ne 1) {
        $reasons.Add('test-window-not-exact-match')
    }
    if ($programProcesses.Count -gt 0) {
        $reasons.Add('program-window-detected')
    }
    if (-not $registryAvailable) {
        $reasons.Add('network-settings-unavailable')
    }
    elseif (-not $configuredPortMatches) {
        $reasons.Add('network-tcp-port-mismatch')
    }
    if ($registryAvailable -and -not $networkAddressIsLoopback) {
        $reasons.Add('network-address-not-loopback')
    }
    if (-not $listenerInspectionAvailable) {
        $reasons.Add('listener-inspection-unavailable')
    }
    elseif ($listeners.Count -eq 0) {
        $reasons.Add('network-server-not-listening')
    }
    else {
        if (-not $listenerAddressesAreLoopback) {
            $reasons.Add('listener-address-not-loopback')
        }
        if (-not $listenerOwnedOnlyByTest) {
            $reasons.Add('listener-not-owned-by-test-instance')
        }
    }

    $ready = $reasons.Count -eq 0
    Write-SafeResult -ExitCode $(if ($ready) { 0 } else { 3 }) -Value ([ordered]@{
        command = 'test-isolation-probe'
        ready = $ready
        expectedTcpPort = $ExpectedTcpPort
        process = [ordered]@{
            inspectionAvailable = $processInspectionAvailable
            totalCount = $tornadoProcesses.Count
            eligibleTestCount = $eligibleProcesses.Count
            programWindowDetected = $programProcesses.Count -gt 0
        }
        networkServer = [ordered]@{
            settingsAvailable = $registryAvailable
            configuredPortMatches = $configuredPortMatches
            addressIsLoopback = $networkAddressIsLoopback
            autoStartEnabled = $networkAutoStart
            listenerInspectionAvailable = $listenerInspectionAvailable
            listenerEndpointCount = $listeners.Count
            listenerAddressesAreLoopback = $listenerAddressesAreLoopback
            listenerOwnedOnlyByTest = $listenerOwnedOnlyByTest
        }
        reasons = @($reasons)
        connectRequestIssued = $false
        comActivationAttempted = $false
        lastKtapConnectState = 'not-attempted'
        ktapConnectAttempted = $false
        networkMonitoringRecordExpected = $false
        networkMonitoringCheckRequired = $false
    })
}
catch {
    Write-SafeResult -ExitCode 70 -Value ([ordered]@{
        command = 'test-isolation-probe'
        ready = $false
        errorCode = 'internal-probe-failure'
        connectRequestIssued = $false
        comActivationAttempted = $false
        ktapConnectAttempted = $false
    })
}
