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

    [string]$Listen = "127.0.0.1:38580",

    [string]$Token = "dev-token",

    [string]$TokenFile = "",

    [string]$DeviceTokensFile = "",

    [string]$DiscoveryListen = "",

    [string]$Advertise = "",

    [switch]$NoDiscovery,

    [switch]$AllowUnsafePublicRelay,

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

    [string]$TransportSecret = "",

    [string]$TransportSecretFile = "",

    [switch]$TestMode
)

$ErrorActionPreference = "Stop"

function Format-SyncVisionServerArgumentsForDisplay {
    param(
        [Parameter(Mandatory = $true)]
        [string[]]$Arguments
    )

    $display = New-Object System.Collections.Generic.List[string]
    $redactNext = $false
    foreach ($argument in $Arguments) {
        if ($redactNext) {
            $display.Add("<redacted>")
            $redactNext = $false
            continue
        }

        if ($argument -eq "--token" -or $argument -eq "--transport-secret") {
            $display.Add($argument)
            $redactNext = $true
            continue
        }

        if ($argument -match '^--token=') {
            $display.Add("--token=<redacted>")
            continue
        }
        if ($argument -match '^--transport-secret=') {
            $display.Add("--transport-secret=<redacted>")
            continue
        }

        if ($argument.IndexOfAny([char[]]" `t`"") -ge 0) {
            $display.Add('"' + ($argument -replace '"', '\"') + '"')
        }
        else {
            $display.Add($argument)
        }
    }

    return $display -join " "
}

function Test-SyncVisionServerLoopbackListen {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Listen
    )

    $separator = $Listen.LastIndexOf(":")
    $hostPart = if ($separator -gt 0) { $Listen.Substring(0, $separator) } else { $Listen }
    return $hostPart -eq "localhost" -or $hostPart -eq "127.0.0.1" -or $hostPart.StartsWith("127.")
}

function Get-SyncVisionServerEndpointHost {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Endpoint
    )

    $separator = $Endpoint.LastIndexOf(":")
    if ($separator -le 0) {
        return $Endpoint
    }

    return $Endpoint.Substring(0, $separator)
}

function Test-SyncVisionServerWildcardListen {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Listen
    )

    $hostPart = Get-SyncVisionServerEndpointHost -Endpoint $Listen
    return $hostPart -eq "0.0.0.0" -or $hostPart -eq "::" -or $hostPart -eq "*" -or [string]::IsNullOrWhiteSpace($hostPart)
}

function Assert-SyncVisionServerTokenAllowed {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Listen,

        [Parameter(Mandatory = $true)]
        [string]$Token,

        [AllowEmptyString()]
        [string]$TokenFile = ""
    )

    if (-not [string]::IsNullOrWhiteSpace($TokenFile)) {
        return
    }

    if ($Token -eq "dev-token" -and -not (Test-SyncVisionServerLoopbackListen -Listen $Listen)) {
        throw "The default dev-token is only allowed for loopback listeners. Set -Token before using -Listen $Listen."
    }
}

function Resolve-SyncVisionServerTokenFilePath {
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$Path
    )

    if ([string]::IsNullOrWhiteSpace($Path)) {
        return ""
    }
    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
        throw "TokenFile was not found: $Path"
    }

    $resolvedPath = (Resolve-Path -LiteralPath $Path).Path
    $tokenText = [IO.File]::ReadAllText($resolvedPath).Trim()
    if ([string]::IsNullOrWhiteSpace($tokenText)) {
        throw "TokenFile must not be empty: $Path"
    }

    return $resolvedPath
}

function Resolve-SyncVisionServerSecretFilePath {
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$Path,

        [Parameter(Mandatory = $true)]
        [string]$Name
    )

    if ([string]::IsNullOrWhiteSpace($Path)) {
        return ""
    }
    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
        throw "$Name was not found: $Path"
    }

    $resolvedPath = (Resolve-Path -LiteralPath $Path).Path
    $secretText = [IO.File]::ReadAllText($resolvedPath).Trim()
    if ([string]::IsNullOrWhiteSpace($secretText)) {
        throw "$Name must not be empty: $Path"
    }

    return $resolvedPath
}

function Assert-SyncVisionServerTransportSecretAllowed {
    param(
        [ValidateSet("disabled", "optional", "required")]
        [string]$TransportEncryption = "disabled",

        [AllowEmptyString()]
        [string]$TransportSecret = "",

        [AllowEmptyString()]
        [string]$TransportSecretFile = ""
    )

    if (-not [string]::IsNullOrWhiteSpace($TransportSecret) -and
        -not [string]::IsNullOrWhiteSpace($TransportSecretFile)) {
        throw "Pass either -TransportSecret or -TransportSecretFile, not both."
    }
    if ($TransportEncryption -ne "disabled" -and
        [string]::IsNullOrWhiteSpace($TransportSecret) -and
        [string]::IsNullOrWhiteSpace($TransportSecretFile)) {
        throw "TransportEncryption $TransportEncryption requires -TransportSecretFile or -TransportSecret."
    }
}

function New-SyncVisionServerArguments {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Listen,

        [string]$Token = "dev-token",

        [AllowEmptyString()]
        [string]$TokenFile = "",

        [AllowEmptyString()]
        [string]$DeviceTokensFile = "",

        [AllowEmptyString()]
        [string]$DiscoveryListen = "",

        [AllowEmptyString()]
        [string]$Advertise = "",

        [switch]$NoDiscovery,

        [switch]$AllowUnsafePublicRelay,

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

        [AllowEmptyString()]
        [string]$TransportSecret = "",

        [AllowEmptyString()]
        [string]$TransportSecretFile = ""
    )

    $arguments = @("--listen", $Listen)
    if (-not [string]::IsNullOrWhiteSpace($TokenFile)) {
        $arguments += @("--token-file", $TokenFile)
    }
    else {
        $arguments += @("--token", $Token)
    }
    if ($DeviceTokensFile) {
        $arguments += @("--device-tokens-file", $DeviceTokensFile)
    }
    if ($NoDiscovery) {
        $arguments += "--no-discovery"
    }
    if ($DiscoveryListen) {
        $arguments += @("--discovery-listen", $DiscoveryListen)
    }
    if ($Advertise) {
        $arguments += @("--advertise", $Advertise)
    }
    if ($AllowUnsafePublicRelay) {
        $arguments += "--allow-unsafe-public-relay"
    }
    if ($TransportEncryption -ne "disabled") {
        $arguments += @("--transport-encryption", $TransportEncryption)
        if (-not [string]::IsNullOrWhiteSpace($TransportSecretFile)) {
            $arguments += @("--transport-secret-file", $TransportSecretFile)
        }
        else {
            $arguments += @("--transport-secret", $TransportSecret)
        }
    }

    return $arguments
}

function Assert-SyncVisionServerDiscoveryAdvertiseAllowed {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Listen,

        [AllowEmptyString()]
        [string]$Advertise,

        [switch]$NoDiscovery
    )

    if (-not $NoDiscovery -and [string]::IsNullOrWhiteSpace($Advertise) -and (Test-SyncVisionServerWildcardListen -Listen $Listen)) {
        throw "LAN discovery cannot advertise wildcard listen address '$Listen'. Pass -Advertise <reachable-host:port>, or pass -NoDiscovery for explicit server-address use."
    }
}

function Resolve-SyncVisionServerNoDiscovery {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Listen,

        [AllowEmptyString()]
        [string]$Advertise,

        [switch]$NoDiscovery
    )

    if ($NoDiscovery) {
        return $true
    }

    if (-not [string]::IsNullOrWhiteSpace($Advertise)) {
        return $false
    }

    return Test-SyncVisionServerWildcardListen -Listen $Listen
}

function Assert-SyncVisionServerUnsafePublicRelayAcknowledged {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Listen,

        [Parameter(Mandatory = $true)]
        [bool]$AllowUnsafePublicRelay
    )

    if (-not (Test-SyncVisionServerLoopbackListen -Listen $Listen) -and -not $AllowUnsafePublicRelay) {
        throw "Non-loopback raw TCP relay requires -AllowUnsafePublicRelay. Use a trusted network, VPN, or TLS tunnel before exposing SyncVision publicly."
    }
}

if ($TestMode) {
    return
}

$repoRoot = Split-Path -Parent $PSScriptRoot
$serverExe = Join-Path $repoRoot "build\x64\$Configuration\SyncVisionServer.exe"

if (-not (Test-Path -LiteralPath $serverExe)) {
    throw "SyncVisionServer.exe was not found at $serverExe. Run scripts\build.ps1 -Configuration $Configuration first."
}

if (-not [string]::IsNullOrWhiteSpace($TokenFile) -and $PSBoundParameters.ContainsKey("Token")) {
    throw "Pass either -Token or -TokenFile, not both."
}

$resolvedTokenFile = Resolve-SyncVisionServerTokenFilePath -Path $TokenFile
$resolvedTransportSecretFile = Resolve-SyncVisionServerSecretFilePath -Path $TransportSecretFile -Name "TransportSecretFile"
$resolvedNoDiscovery = Resolve-SyncVisionServerNoDiscovery -Listen $Listen -Advertise $Advertise -NoDiscovery:$NoDiscovery

Assert-SyncVisionServerTokenAllowed -Listen $Listen -Token $Token -TokenFile $resolvedTokenFile
Assert-SyncVisionServerTransportSecretAllowed -TransportEncryption $TransportEncryption -TransportSecret $TransportSecret -TransportSecretFile $resolvedTransportSecretFile
Assert-SyncVisionServerDiscoveryAdvertiseAllowed -Listen $Listen -Advertise $Advertise -NoDiscovery:$resolvedNoDiscovery
Assert-SyncVisionServerUnsafePublicRelayAcknowledged -Listen $Listen -AllowUnsafePublicRelay ([bool]$AllowUnsafePublicRelay)

$arguments = New-SyncVisionServerArguments `
    -Listen $Listen `
    -Token $Token `
    -TokenFile $resolvedTokenFile `
    -DeviceTokensFile $DeviceTokensFile `
    -DiscoveryListen $DiscoveryListen `
    -Advertise $Advertise `
    -NoDiscovery:$resolvedNoDiscovery `
    -AllowUnsafePublicRelay:$AllowUnsafePublicRelay `
    -TransportEncryption $TransportEncryption `
    -TransportSecret $TransportSecret `
    -TransportSecretFile $resolvedTransportSecretFile

Write-Host "Starting SyncVisionServer.exe $(Format-SyncVisionServerArgumentsForDisplay -Arguments $arguments)"
& $serverExe @arguments

if ($null -ne $LASTEXITCODE) {
    exit $LASTEXITCODE
}
