param(
    [string]$Server = "",

    [string]$TokenFile = "",

    [ValidateSet("Debug", "Release")]
    [string]$Configuration = "Release",

    [switch]$SkipBuild,

    [ValidateSet("disabled", "optional", "required")]
    [string]$TransportEncryption = "required",

    [string]$TransportSecretFile = "",

    [int]$TimeoutMs = 5000,

    [int]$TrafficTimeoutSeconds = 30,

    [switch]$AllowPrivateRelayAddress,

    [switch]$TestMode
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$scriptDir = Split-Path -Parent $PSCommandPath
$repoRoot = Split-Path -Parent $scriptDir
$verifyScript = Join-Path $scriptDir "verify-public-relay.ps1"

$e2eServer = $Server
$e2eTokenFile = $TokenFile
$e2eConfiguration = $Configuration
$skipBuildRequested = [bool]$SkipBuild
$e2eTransportEncryption = $TransportEncryption
$e2eTransportSecretFile = $TransportSecretFile
$e2eTimeoutMs = $TimeoutMs
$e2eTrafficTimeoutSeconds = $TrafficTimeoutSeconds
$allowPrivateRelayAddressRequested = [bool]$AllowPrivateRelayAddress
$testModeRequested = [bool]$TestMode

. $verifyScript -TestMode

$Server = $e2eServer
$TokenFile = $e2eTokenFile
$Configuration = $e2eConfiguration
$TransportEncryption = $e2eTransportEncryption
$TransportSecretFile = $e2eTransportSecretFile
$TimeoutMs = $e2eTimeoutMs
$TrafficTimeoutSeconds = $e2eTrafficTimeoutSeconds

function ConvertTo-SvArgument {
    param([string]$Argument)

    if ($Argument.Length -eq 0) {
        return '""'
    }
    if ($Argument.IndexOfAny([char[]]" `t`"") -lt 0) {
        return $Argument
    }

    return '"' + ($Argument -replace '"', '\"') + '"'
}

function Join-SvArgumentList {
    param([string[]]$Arguments)

    return (($Arguments | ForEach-Object { ConvertTo-SvArgument $_ }) -join " ")
}

function Stop-SvProcess {
    param([System.Diagnostics.Process]$Process)

    if ($Process -and -not $Process.HasExited) {
        try {
            Stop-Process -Id $Process.Id -Force -ErrorAction SilentlyContinue
            if (-not $Process.WaitForExit(3000)) {
                Write-Warning "Timed out waiting for process $($Process.Id) to exit."
            }
        }
        catch {
            Write-Warning "Failed to stop process $($Process.Id): $($_.Exception.Message)"
        }
    }
}

function Get-LogTail {
    param([string]$Path)

    if (-not (Test-Path -LiteralPath $Path)) {
        return "<missing: $Path>"
    }

    return (Get-Content -LiteralPath $Path -Tail 80) -join [Environment]::NewLine
}

function Get-ProcessLogSummary {
    param(
        [string]$Name,
        [string]$OutLog,
        [string]$ErrLog
    )

    return "$Name stdout:`n$(Get-LogTail $OutLog)`n$Name stderr:`n$(Get-LogTail $ErrLog)"
}

function Get-AllProcessLogSummary {
    param(
        [string]$HostOutLog,
        [string]$HostErrLog,
        [string]$ViewerOutLog,
        [string]$ViewerErrLog
    )

    return @(
        (Get-ProcessLogSummary -Name "host" -OutLog $HostOutLog -ErrLog $HostErrLog),
        (Get-ProcessLogSummary -Name "viewer" -OutLog $ViewerOutLog -ErrLog $ViewerErrLog)
    ) -join [Environment]::NewLine
}

function Assert-SvProcessesStillRunning {
    param(
        [AllowNull()]
        [System.Diagnostics.Process]$HostProcess,

        [AllowNull()]
        [System.Diagnostics.Process]$ViewerProcess,

        [string]$HostOutLog,

        [string]$HostErrLog,

        [string]$ViewerOutLog,

        [string]$ViewerErrLog
    )

    $logs = Get-AllProcessLogSummary `
        -HostOutLog $HostOutLog `
        -HostErrLog $HostErrLog `
        -ViewerOutLog $ViewerOutLog `
        -ViewerErrLog $ViewerErrLog

    if ($HostProcess -and $HostProcess.HasExited) {
        throw "host exited early with code $($HostProcess.ExitCode). $logs"
    }
    if ($ViewerProcess -and $ViewerProcess.HasExited) {
        throw "viewer exited early with code $($ViewerProcess.ExitCode). $logs"
    }
}

function Test-LogContains {
    param(
        [string]$Path,
        [string]$Text
    )

    if (-not (Test-Path -LiteralPath $Path)) {
        return $false
    }

    return (Select-String -LiteralPath $Path -SimpleMatch $Text -Quiet) -eq $true
}

function Wait-SvLogText {
    param(
        [string]$Path,
        [string]$Text,
        [AllowNull()]
        [System.Diagnostics.Process]$HostProcess,
        [AllowNull()]
        [System.Diagnostics.Process]$ViewerProcess,
        [string]$HostOutLog,
        [string]$HostErrLog,
        [string]$ViewerOutLog,
        [string]$ViewerErrLog,
        [int]$TimeoutSeconds
    )

    $deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
    while ([DateTime]::UtcNow -lt $deadline) {
        Assert-SvProcessesStillRunning `
            -HostProcess $HostProcess `
            -ViewerProcess $ViewerProcess `
            -HostOutLog $HostOutLog `
            -HostErrLog $HostErrLog `
            -ViewerOutLog $ViewerOutLog `
            -ViewerErrLog $ViewerErrLog
        if (Test-LogContains -Path $Path -Text $Text) {
            return
        }
        Start-Sleep -Milliseconds 100
    }

    throw "Timed out waiting for '$Text' in $Path. $(Get-AllProcessLogSummary -HostOutLog $HostOutLog -HostErrLog $HostErrLog -ViewerOutLog $ViewerOutLog -ViewerErrLog $ViewerErrLog)"
}

function Invoke-SvPublicRelayVerifier {
    param(
        [int]$MinHosts = 0,
        [int]$MinViewers = 0,
        [UInt64]$MinRelayedFileMessages = 0
    )

    $hasThresholds = $MinHosts -gt 0 -or $MinViewers -gt 0 -or $MinRelayedFileMessages -gt 0
    if ($hasThresholds) {
        if (-not [string]::IsNullOrWhiteSpace($TransportSecretFile)) {
            & $verifyScript `
                -Server $Server `
                -TokenFile $TokenFile `
                -TransportEncryption $TransportEncryption `
                -TransportSecretFile $TransportSecretFile `
                -TimeoutMs $TimeoutMs `
                -AllowPrivateRelayAddress:$allowPrivateRelayAddressRequested `
                -MinHosts $MinHosts `
                -MinViewers $MinViewers `
                -MinRelayedFileMessages $MinRelayedFileMessages
            return
        }

        & $verifyScript `
            -Server $Server `
            -TokenFile $TokenFile `
            -TransportEncryption $TransportEncryption `
            -TimeoutMs $TimeoutMs `
            -AllowPrivateRelayAddress:$allowPrivateRelayAddressRequested `
            -MinHosts $MinHosts `
            -MinViewers $MinViewers `
            -MinRelayedFileMessages $MinRelayedFileMessages
        return
    }

    if (-not [string]::IsNullOrWhiteSpace($TransportSecretFile)) {
        & $verifyScript `
            -Server $Server `
            -TokenFile $TokenFile `
            -TransportEncryption $TransportEncryption `
            -TransportSecretFile $TransportSecretFile `
            -TimeoutMs $TimeoutMs `
            -AllowPrivateRelayAddress:$allowPrivateRelayAddressRequested
        return
    }

    & $verifyScript `
        -Server $Server `
        -TokenFile $TokenFile `
        -TransportEncryption $TransportEncryption `
        -TimeoutMs $TimeoutMs `
        -AllowPrivateRelayAddress:$allowPrivateRelayAddressRequested
}

function Wait-SvPublicRelayVerifier {
    param(
        [int]$MinHosts = 0,
        [int]$MinViewers = 0,
        [UInt64]$MinRelayedFileMessages = 0,
        [AllowNull()]
        [System.Diagnostics.Process]$HostProcess,
        [AllowNull()]
        [System.Diagnostics.Process]$ViewerProcess,
        [string]$HostOutLog,
        [string]$HostErrLog,
        [string]$ViewerOutLog,
        [string]$ViewerErrLog,
        [int]$TimeoutSeconds
    )

    $lastError = $null
    $deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
    while ([DateTime]::UtcNow -lt $deadline) {
        Assert-SvProcessesStillRunning `
            -HostProcess $HostProcess `
            -ViewerProcess $ViewerProcess `
            -HostOutLog $HostOutLog `
            -HostErrLog $HostErrLog `
            -ViewerOutLog $ViewerOutLog `
            -ViewerErrLog $ViewerErrLog
        try {
            Invoke-SvPublicRelayVerifier `
                -MinHosts $MinHosts `
                -MinViewers $MinViewers `
                -MinRelayedFileMessages $MinRelayedFileMessages
            return
        }
        catch {
            $lastError = $_.Exception.Message
            Start-Sleep -Milliseconds 500
        }
    }

    throw "Timed out waiting for public relay status thresholds. Last verifier error: $lastError`n$(Get-AllProcessLogSummary -HostOutLog $HostOutLog -HostErrLog $HostErrLog -ViewerOutLog $ViewerOutLog -ViewerErrLog $ViewerErrLog)"
}

if ($testModeRequested) {
    return
}

if ([string]::IsNullOrWhiteSpace($Server)) {
    throw "Public relay E2E smoke requires -Server <host:port>."
}
if ([string]::IsNullOrWhiteSpace($TokenFile)) {
    throw "Public relay E2E smoke requires -TokenFile."
}
if (-not (Test-Path -LiteralPath $TokenFile -PathType Leaf)) {
    throw "Public relay E2E token file was not found: $TokenFile"
}
$TokenFile = (Resolve-Path -LiteralPath $TokenFile).Path
if ($TransportEncryption -ne "disabled") {
    if ([string]::IsNullOrWhiteSpace($TransportSecretFile)) {
        throw "TransportEncryption $TransportEncryption requires -TransportSecretFile."
    }
    if (-not (Test-Path -LiteralPath $TransportSecretFile -PathType Leaf)) {
        throw "Public relay E2E transport secret file was not found: $TransportSecretFile"
    }
    $TransportSecretFile = (Resolve-Path -LiteralPath $TransportSecretFile).Path
}

Assert-SyncVisionPublicRelayEndpointAllowed -Server $Server -AllowPrivateRelayAddress:$allowPrivateRelayAddressRequested

$runtimeDir = Join-Path $repoRoot "build\x64\$Configuration"
$privateClientExe = Join-Path $runtimeDir "private-runtime\SyncVisionClient.exe"
$clientExe = if (Test-Path -LiteralPath $privateClientExe -PathType Leaf) {
    $privateClientExe
}
else {
    Join-Path $runtimeDir "SyncVisionClient.exe"
}
$smokeRoot = Join-Path $repoRoot "build\public relay e2e smoke"
$sendDir = Join-Path $smokeRoot "send"
$receiveDir = Join-Path $smokeRoot "receive"
$hostOutLog = Join-Path $smokeRoot "host.out.log"
$hostErrLog = Join-Path $smokeRoot "host.err.log"
$viewerOutLog = Join-Path $smokeRoot "viewer.out.log"
$viewerErrLog = Join-Path $smokeRoot "viewer.err.log"

$hostProcess = $null
$viewerProcess = $null

Push-Location $repoRoot
try {
    if (-not $skipBuildRequested) {
        powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build.ps1 -Configuration $Configuration
        if ($LASTEXITCODE -ne 0) {
            exit $LASTEXITCODE
        }
    }

    if (-not (Test-Path -LiteralPath $clientExe -PathType Leaf)) {
        throw "SyncVisionClient.exe was not found: $clientExe"
    }

    if (Test-Path -LiteralPath $smokeRoot) {
        Remove-Item -LiteralPath $smokeRoot -Recurse -Force
    }
    New-Item -ItemType Directory -Force -Path $sendDir, $receiveDir | Out-Null

    $suffix = [Guid]::NewGuid().ToString("N").Substring(0, 12)
    $HostDevice = "host-public-e2e-$suffix"
    $ViewerDevice = "viewer-public-e2e-$suffix"
    $fileName = "syncvision-public-relay-e2e-$suffix.txt"
    $sourcePath = Join-Path $sendDir $fileName
    $fileBytes = [Text.Encoding]::UTF8.GetBytes("hello from syncvision public relay e2e smoke $suffix`n")
    [IO.File]::WriteAllBytes($sourcePath, $fileBytes)

    Invoke-SvPublicRelayVerifier

    $hostArguments = @("--mode", "host", "--server", $Server, "--device", $HostDevice, "--token-file", $TokenFile, "--receive-dir", $receiveDir, "--capture-fps", "1")
    if ($TransportEncryption -ne "disabled") {
        $hostArguments += @("--transport-encryption", $TransportEncryption, "--transport-secret-file", $TransportSecretFile)
    }
    $hostProcess = Start-Process -FilePath $clientExe `
        -ArgumentList (Join-SvArgumentList $hostArguments) `
        -RedirectStandardOutput $hostOutLog `
        -RedirectStandardError $hostErrLog `
        -PassThru `
        -WindowStyle Hidden

    Wait-SvPublicRelayVerifier `
        -MinHosts 1 `
        -HostProcess $hostProcess `
        -ViewerProcess $viewerProcess `
        -HostOutLog $hostOutLog `
        -HostErrLog $hostErrLog `
        -ViewerOutLog $viewerOutLog `
        -ViewerErrLog $viewerErrLog `
        -TimeoutSeconds $TrafficTimeoutSeconds

    $viewerArguments = @("--mode", "viewer", "--server", $Server, "--device", $ViewerDevice, "--target", $HostDevice, "--token-file", $TokenFile, "--send-file", $sourcePath)
    if ($TransportEncryption -ne "disabled") {
        $viewerArguments += @("--transport-encryption", $TransportEncryption, "--transport-secret-file", $TransportSecretFile)
    }
    $viewerProcess = Start-Process -FilePath $clientExe `
        -ArgumentList (Join-SvArgumentList $viewerArguments) `
        -RedirectStandardOutput $viewerOutLog `
        -RedirectStandardError $viewerErrLog `
        -PassThru `
        -WindowStyle Hidden

    Wait-SvPublicRelayVerifier `
        -MinHosts 1 `
        -MinViewers 1 `
        -HostProcess $hostProcess `
        -ViewerProcess $viewerProcess `
        -HostOutLog $hostOutLog `
        -HostErrLog $hostErrLog `
        -ViewerOutLog $viewerOutLog `
        -ViewerErrLog $viewerErrLog `
        -TimeoutSeconds $TrafficTimeoutSeconds

    $receivedPath = Join-Path $receiveDir $fileName
    $deadline = [DateTime]::UtcNow.AddSeconds($TrafficTimeoutSeconds)
    while ([DateTime]::UtcNow -lt $deadline -and -not (Test-Path -LiteralPath $receivedPath)) {
        Assert-SvProcessesStillRunning `
            -HostProcess $hostProcess `
            -ViewerProcess $viewerProcess `
            -HostOutLog $hostOutLog `
            -HostErrLog $hostErrLog `
            -ViewerOutLog $viewerOutLog `
            -ViewerErrLog $viewerErrLog
        Start-Sleep -Milliseconds 100
    }

    if (-not (Test-Path -LiteralPath $receivedPath)) {
        throw "Timed out waiting for received file: $receivedPath`n$(Get-AllProcessLogSummary -HostOutLog $hostOutLog -HostErrLog $hostErrLog -ViewerOutLog $viewerOutLog -ViewerErrLog $viewerErrLog)"
    }

    $receivedBytes = [IO.File]::ReadAllBytes($receivedPath)
    if ([Convert]::ToBase64String($receivedBytes) -ne [Convert]::ToBase64String($fileBytes)) {
        throw "Received file content mismatch.`n$(Get-AllProcessLogSummary -HostOutLog $hostOutLog -HostErrLog $hostErrLog -ViewerOutLog $viewerOutLog -ViewerErrLog $viewerErrLog)"
    }

    Wait-SvPublicRelayVerifier `
        -MinHosts 1 `
        -MinViewers 1 `
        -MinRelayedFileMessages 1 `
        -HostProcess $hostProcess `
        -ViewerProcess $viewerProcess `
        -HostOutLog $hostOutLog `
        -HostErrLog $hostErrLog `
        -ViewerOutLog $viewerOutLog `
        -ViewerErrLog $viewerErrLog `
        -TimeoutSeconds $TrafficTimeoutSeconds

    Write-Host "SyncVision public relay E2E smoke passed."
    Write-Host "Server: $Server"
    Write-Host "HostDevice: $HostDevice"
    Write-Host "ViewerDevice: $ViewerDevice"
    Write-Host "Bytes: $($receivedBytes.Length)"
    Write-Host "ProbeScope: external client network running this script; this proves reachability and relay traffic only from this client network path."
}
finally {
    Stop-SvProcess -Process $viewerProcess
    Stop-SvProcess -Process $hostProcess
    Pop-Location
}
