[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [ValidateSet('127.0.0.1', '::1')]
    [string] $PlayoutHost,

    [Parameter(Mandatory)]
    [ValidateRange(1, 65535)]
    [int] $PlayoutPort,

    [Parameter(Mandatory)]
    [ValidatePattern('^[0-9A-Fa-f]{64}$')]
    [string] $NativeSha256,

    [Parameter(Mandatory)]
    [ValidatePattern('^[0-9A-Fa-f]{64}$')]
    [string] $InteropSha256,

    [ValidateRange(0, 2147483647)]
    [Nullable[int]] $OutputChannel = $null,

    [switch] $Force,

    [string] $ConfigurationDirectory = (
        Join-Path $env:LOCALAPPDATA 'MBN_STOCK_WEBVIEW\Config')
)

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

$requiredAuthorization = 'I_AUTHORIZE_LIVE_PROGRAM_OUTPUT_FOR_THIS_LAUNCH'
$sceneAllowlist = @(
    '5001', '5006', '5011', '5016', '50160', '5023', '5024', '5025',
    '5026', '5029', '5032', '5037', '5068', '5070', '5072', '5074',
    '5076', '5077', '5078', '5079', '5080', '5081', '5082', '5083',
    '5084', '5085', '5086', '50860', '5087', '5088', '6001', '6067',
    '8001', '8002', '8003', '8018', '8032', '8035', '8040', '8046',
    '8051', '8056', '8061', '8067', 'N5001'
)

function Assert-LocalAppDataPath {
    param([Parameter(Mandatory)][string] $Path)

    $localRoot = [IO.Path]::GetFullPath(
        [Environment]::GetFolderPath(
            [Environment+SpecialFolder]::LocalApplicationData))
    $fullPath = [IO.Path]::GetFullPath($Path)
    $rootPrefix = $localRoot.TrimEnd(
        [IO.Path]::DirectorySeparatorChar,
        [IO.Path]::AltDirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar

    if (-not $fullPath.StartsWith(
            $rootPrefix,
            [StringComparison]::OrdinalIgnoreCase)) {
        throw 'Development Live configuration must stay under LocalAppData.'
    }

    return $fullPath
}

function Assert-NoReparsePointChain {
    param(
        [Parameter(Mandatory)][string] $Directory,
        [Parameter(Mandatory)][string] $StopDirectory
    )

    $current = [IO.Path]::GetFullPath($Directory)
    $stop = [IO.Path]::GetFullPath($StopDirectory)
    while (-not [string]::Equals(
            $current,
            $stop,
            [StringComparison]::OrdinalIgnoreCase)) {
        $item = Get-Item -LiteralPath $current -Force
        if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
            throw 'Development Live configuration directories cannot be reparse points.'
        }

        $parent = [IO.Path]::GetDirectoryName($current)
        if ([string]::IsNullOrWhiteSpace($parent) -or
            [string]::Equals(
                $parent,
                $current,
                [StringComparison]::OrdinalIgnoreCase)) {
            throw 'The LocalAppData configuration directory chain is invalid.'
        }

        $current = $parent
    }
}

function Assert-WritableTarget {
    param([Parameter(Mandatory)][string] $Path)

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

    $item = Get-Item -LiteralPath $Path -Force
    if ($item.PSIsContainer -or
        ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
        throw 'Development Live configuration targets must be ordinary files.'
    }

    if (-not $Force) {
        throw "Configuration already exists. Review it and rerun with -Force: $Path"
    }
}

function Set-CurrentUserOnlyAcl {
    param([Parameter(Mandatory)][string] $Path)

    $identity = [Security.Principal.WindowsIdentity]::GetCurrent().Name
    $acl = [Security.AccessControl.FileSecurity]::new()
    $acl.SetAccessRuleProtection($true, $false)
    $rule = [Security.AccessControl.FileSystemAccessRule]::new(
        $identity,
        [Security.AccessControl.FileSystemRights]::FullControl,
        [Security.AccessControl.AccessControlType]::Allow)
    $acl.AddAccessRule($rule)
    [IO.File]::SetAccessControl($Path, $acl)
}

function Invalidate-ExistingAuthorization {
    param([Parameter(Mandatory)][string] $Path)

    if (-not [IO.File]::Exists($Path)) {
        return
    }

    $invalidatedPath = (
        $Path + '.invalidated.' + [Guid]::NewGuid().ToString('N'))
    [IO.File]::Move($Path, $invalidatedPath)
    try {
        Set-CurrentUserOnlyAcl $invalidatedPath
        [IO.File]::Delete($invalidatedPath)
    }
    catch {
        throw (
            'The previous Development Live authorization was invalidated but ' +
            "could not be removed. Remove this protected file before retrying: $invalidatedPath")
    }

    if ([IO.File]::Exists($Path)) {
        throw 'The previous Development Live authorization could not be invalidated.'
    }
}

function Write-ProtectedJson {
    param(
        [Parameter(Mandatory)][string] $Path,
        [Parameter(Mandatory)][string] $Json
    )

    $temporaryPath = "$Path.tmp.$([Guid]::NewGuid().ToString('N'))"
    try {
        [IO.File]::WriteAllText(
            $temporaryPath,
            $Json,
            [Text.UTF8Encoding]::new($false))
        Set-CurrentUserOnlyAcl $temporaryPath
        Move-Item -LiteralPath $temporaryPath -Destination $Path -Force
    }
    finally {
        if ([IO.File]::Exists($temporaryPath)) {
            Remove-Item -LiteralPath $temporaryPath -Force
        }
    }
}

$configurationRoot = Assert-LocalAppDataPath $ConfigurationDirectory
[IO.Directory]::CreateDirectory($configurationRoot) | Out-Null
$localApplicationData = [IO.Path]::GetFullPath(
    [Environment]::GetFolderPath(
        [Environment+SpecialFolder]::LocalApplicationData))
Assert-NoReparsePointChain $configurationRoot $localApplicationData

$playoutPath = Join-Path $configurationRoot 'playout.local.json'
$authorizationPath = Join-Path $configurationRoot 'playout.development-live.local.json'
Assert-WritableTarget $playoutPath
Assert-WritableTarget $authorizationPath

$playout = [ordered]@{
    mode = 'Live'
    host = $PlayoutHost
    port = $PlayoutPort
    tcpMode = 1
    clientPort = 0
    sceneDirectory = $null
    outputChannel = $OutputChannel
    layoutIndex = 10
    legacySceneFadeDuration = 6
    legacySceneBackgroundKind = 'None'
    legacySceneBackgroundAssetPath = $null
    legacySceneBackgroundVideoLoopCount = 2004
    legacySceneBackgroundVideoLoopInfinite = $true
    legacyBackgroundDirectory = $null
    testProcessWindowTitlePattern = $null
    testSceneAllowlist = $sceneAllowlist
    trustedLiveOutputEnabled = $true
    queueCapacity = 64
    connectTimeoutMilliseconds = 5000
    operationTimeoutMilliseconds = 5000
    disconnectTimeoutMilliseconds = 3000
    processPollIntervalMilliseconds = 1000
    reconnectDelayMilliseconds = 1000
    maximumReconnectAttempts = 0
    reconnectEnabled = $false
    maximumAutomaticRefreshesPerTakeIn = 0
}

$authorization = [ordered]@{
    schemaVersion = 1
    mode = 'Live'
    authorization = $requiredAuthorization
    nativeSha256 = $NativeSha256.ToUpperInvariant()
    interopSha256 = $InteropSha256.ToUpperInvariant()
}

$playoutJson = $playout | ConvertTo-Json -Depth 6
$authorizationJson = $authorization | ConvertTo-Json -Depth 3

# In Force mode, invalidate the old authorization before changing the protected
# Live profile. Any later failure therefore leaves no valid authorization at the
# exact path consumed by the bootstrap.
Invalidate-ExistingAuthorization -Path $authorizationPath

# Write the protected Live profile first and the launch authorization last.
# Merely creating these files does not connect: vendor hash verification,
# launch authorization, target validation, and runtime gates remain mandatory.
Write-ProtectedJson $playoutPath $playoutJson
Write-ProtectedJson $authorizationPath $authorizationJson

Write-Host "Development Live base configuration created at: $playoutPath"
Write-Host "Development Live launch authorization created at: $authorizationPath"
Write-Warning (
    'Debug and Release launches now use the protected Live profile. ' +
    'Confirm the local development PGM target before starting the application.')
