[CmdletBinding()]
param()

$ErrorActionPreference = 'Stop'

# Manually enter Horizon API credentials here when agent status lookup is needed.
$Username = ''
$Domain = ''
$Password = ''

# Logging configuration. Change to INFO if you want less console and file output.
$LogLevel = 'DEBUG'

$script:LogLevelRank = @{
    DEBUG   = 0
    INFO    = 1
    SUCCESS = 1
    WARN    = 2
    ERROR   = 3
}

$script:MinimumLogLevel = if ([string]::IsNullOrWhiteSpace($LogLevel)) { 'DEBUG' } else { $LogLevel.ToUpperInvariant() }
if (-not $script:LogLevelRank.ContainsKey($script:MinimumLogLevel)) {
    $script:MinimumLogLevel = 'DEBUG'
}

$script:ExecutionStartedAt = Get-Date
$script:LocalServerName = if (-not [string]::IsNullOrWhiteSpace($env:COMPUTERNAME)) {
    $env:COMPUTERNAME
}
else {
    [System.Net.Dns]::GetHostName()
}
$script:ScriptPath = if (-not [string]::IsNullOrWhiteSpace($PSCommandPath)) {
    $PSCommandPath
}
elseif (-not [string]::IsNullOrWhiteSpace($MyInvocation.MyCommand.Path)) {
    $MyInvocation.MyCommand.Path
}
else {
    Join-Path -Path (Get-Location).Path -ChildPath 'Get-HorizonBroker.ps1'
}

$script:ScriptDirectory = Split-Path -Path $script:ScriptPath -Parent
$script:ScriptBaseName = [System.IO.Path]::GetFileNameWithoutExtension($script:ScriptPath)
if ([string]::IsNullOrWhiteSpace($script:ScriptBaseName)) {
    $script:ScriptBaseName = 'Get-HorizonBroker'
}

$script:LogFilePath = Join-Path -Path $script:ScriptDirectory -ChildPath ('{0}_{1}.log' -f $script:ScriptBaseName, $script:ExecutionStartedAt.ToString('yyyyMMdd_HHmmss'))
$script:OperationsLogDirectory = 'C:\Operations'
$script:OperationsLogPath = Join-Path -Path $script:OperationsLogDirectory -ChildPath 'getHorizonBroker.log'
$script:LogWriteFailureNotified = $false

try {
    $null = New-Item -Path $script:LogFilePath -ItemType File -Force
}
catch {
    $script:LogWriteFailureNotified = $true
    $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    Write-Host "[$timestamp] [WARN] Unable to initialize log file '$script:LogFilePath'. $($_.Exception.Message)"
}

function Enable-InsecureTlsValidation {
    Write-Log 'Configuring TLS protocols and disabling certificate validation for Horizon endpoint checks.' 'DEBUG'

    try {
        Add-Type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;

public static class TrustAllCertsPolicy
{
    public static bool IgnoreValidation(
        object sender,
        X509Certificate certificate,
        X509Chain chain,
        System.Net.Security.SslPolicyErrors sslPolicyErrors)
    {
        return true;
    }
}
"@ -ErrorAction SilentlyContinue
    }
    catch {
        # Type may already exist in the current session; that is safe to ignore.
    }

    $protocols = 0
    foreach ($protocolName in @('Tls13', 'Tls12', 'Tls11', 'Tls')) {
        try {
            $protocolValue = [System.Enum]::Parse([System.Net.SecurityProtocolType], $protocolName)
            $protocols = $protocols -bor [int]$protocolValue
            Write-Log "TLS protocol '$protocolName' is supported in this session." 'DEBUG'
        }
        catch {
            Write-Log "TLS protocol '$protocolName' is not supported in this PowerShell/.NET runtime." 'DEBUG'
        }
    }

    if ($protocols -eq 0) {
        throw 'No supported TLS protocols were available for outbound HTTPS requests.'
    }

    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]$protocols

    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { param($sender, $certificate, $chain, $sslPolicyErrors) return $true }
    [System.Net.ServicePointManager]::Expect100Continue = $false
    Write-Log "TLS certificate validation is disabled for this session. Enabled protocols: $([System.Net.ServicePointManager]::SecurityProtocol)"
}

function Write-Log {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [ValidateSet('DEBUG', 'INFO', 'WARN', 'ERROR', 'SUCCESS')]
        [string]$Level = 'INFO'
    )

    $normalizedLevel = $Level.ToUpperInvariant()
    if ($script:LogLevelRank[$normalizedLevel] -lt $script:LogLevelRank[$script:MinimumLogLevel]) {
        return
    }

    $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    $logEntry = "[$timestamp] [$normalizedLevel] $Message"
    Write-Host $logEntry

    if (-not [string]::IsNullOrWhiteSpace($script:LogFilePath)) {
        try {
            Add-Content -LiteralPath $script:LogFilePath -Value $logEntry -Encoding UTF8
        }
        catch {
            if (-not $script:LogWriteFailureNotified) {
                $script:LogWriteFailureNotified = $true
                Write-Host "[$timestamp] [WARN] Unable to write to log file '$script:LogFilePath'. $($_.Exception.Message)"
            }
        }
    }
}

function Ensure-OperationsLogFile {
    Write-Log "Ensuring operations log directory exists at '$script:OperationsLogDirectory'." 'DEBUG'
    if (-not (Test-Path -Path $script:OperationsLogDirectory -PathType Container)) {
        $null = New-Item -Path $script:OperationsLogDirectory -ItemType Directory -Force
        Write-Log "Created operations log directory '$script:OperationsLogDirectory'." 'DEBUG'
    }

    Write-Log "Ensuring operations log file exists at '$script:OperationsLogPath'." 'DEBUG'
    if (-not (Test-Path -Path $script:OperationsLogPath -PathType Leaf)) {
        $null = New-Item -Path $script:OperationsLogPath -ItemType File -Force
        Write-Log "Created operations log file '$script:OperationsLogPath'." 'DEBUG'
    }
}

function Write-OperationsSummaryLog {
    param(
        [AllowNull()]
        [string]$ConnectionServerName,

        [AllowNull()]
        [string]$AgentStatusValue
    )

    try {
        Ensure-OperationsLogFile

        $existingEntries = @()
        if (Test-Path -Path $script:OperationsLogPath -PathType Leaf) {
            $existingEntries = @(Get-Content -Path $script:OperationsLogPath -ErrorAction Stop)
        }

        Write-Log "Operations log currently contains $($existingEntries.Count) entr$(if ($existingEntries.Count -eq 1) { 'y' } else { 'ies' })." 'DEBUG'
        while ($existingEntries.Count -ge 1000) {
            $existingEntries = @($existingEntries | Select-Object -Skip 1)
            Write-Log 'Trimmed the oldest entry from the operations log to maintain the 1000-entry limit.' 'DEBUG'
        }

        $summaryEntry = '{0} | LocalServerName={1} | HorizonConnectionServer={2} | AgentStatus={3}' -f `
            $script:ExecutionStartedAt.ToString('yyyy-MM-dd HH:mm:ss'), `
            (Format-DisplayValue -Value $script:LocalServerName), `
            (Format-DisplayValue -Value $ConnectionServerName), `
            (Format-DisplayValue -Value $AgentStatusValue)

        if ($existingEntries.Count -gt 0) {
            Set-Content -Path $script:OperationsLogPath -Value (@($existingEntries) + $summaryEntry) -Encoding UTF8
        }
        else {
            Set-Content -Path $script:OperationsLogPath -Value $summaryEntry -Encoding UTF8
        }

        Write-Log "Appended operations summary log entry to '$script:OperationsLogPath'." 'DEBUG'
    }
    catch {
        Write-Log "Unable to update operations summary log '$script:OperationsLogPath'. $($_.Exception.Message)" 'WARN'
    }
}

function Get-RegistryStringValue {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,

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

    Write-Log "Reading registry value '$Name' from '$Path'." 'DEBUG'

    try {
        if (-not (Test-Path -Path $Path)) {
            Write-Log "Registry path not found: $Path" 'WARN'
            return $null
        }

        $item = Get-ItemProperty -Path $Path -Name $Name -ErrorAction Stop
        $value = $item.$Name

        if ([string]::IsNullOrWhiteSpace($value)) {
            Write-Log "Registry value '$Name' at '$Path' is empty." 'WARN'
            return $null
        }

        Write-Log "Found registry value '$Name' at '$Path': $value"
        return [string]$value
    }
    catch {
        Write-Log "Unable to read '$Name' from '$Path'. $($_.Exception.Message)" 'WARN'
        return $null
    }
}

function Split-BrokerNames {
    param(
        [AllowNull()]
        [string]$Value,

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

    if ([string]::IsNullOrWhiteSpace($Value)) {
        return @()
    }

    Write-Log "Splitting broker names from raw value '$Value' provided by $SourceLabel." 'DEBUG'
    $brokerNames = @(
        $Value -split '[,;\s]+' |
            ForEach-Object { $_.Trim() } |
            Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
            Select-Object -Unique
    )

    if ($brokerNames.Count -eq 0) {
        Write-Log "No broker names were parsed from $SourceLabel." 'WARN'
        return @()
    }

    Write-Log "Parsed broker names from ${SourceLabel}: $($brokerNames -join ', ')"
    return $brokerNames
}

function Get-RegistryBrokerValues {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,

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

    Write-Log "Loading broker values from registry path '$Path' with value name '$Name'." 'DEBUG'
    $value = Get-RegistryStringValue -Path $Path -Name $Name
    if ([string]::IsNullOrWhiteSpace($value)) {
        return @()
    }

    return Split-BrokerNames -Value $value -SourceLabel "registry value '$Name' at '$Path'"
}

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

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

        [hashtable]$QueryParameters
    )

    $builder = New-Object System.UriBuilder('https', $BrokerName)
    $builder.Path = $Path.TrimStart('/')

    if ($null -ne $QueryParameters -and $QueryParameters.Count -gt 0) {
        $queryParts = New-Object System.Collections.Generic.List[string]

        foreach ($queryKey in $QueryParameters.Keys) {
            $queryValue = $QueryParameters[$queryKey]
            if ($null -eq $queryValue) {
                continue
            }

            $stringValue = [string]$queryValue
            if ([string]::IsNullOrWhiteSpace($stringValue)) {
                continue
            }

            [void]$queryParts.Add(('{0}={1}' -f [System.Uri]::EscapeDataString([string]$queryKey), [System.Uri]::EscapeDataString($stringValue)))
        }

        $builder.Query = $queryParts -join '&'
    }

    $absoluteUri = $builder.Uri.AbsoluteUri
    Write-Log "Built Horizon URI: $absoluteUri" 'DEBUG'
    return $absoluteUri
}

function ConvertTo-HorizonRecordArray {
    param(
        [AllowNull()]
        [object]$Value
    )

    if ($null -eq $Value) {
        Write-Log 'Converting null Horizon response payload into an empty record array.' 'DEBUG'
        return @()
    }

    if ($Value -is [System.Array]) {
        Write-Log "Horizon response payload is already an array with $($Value.Count) record(s)." 'DEBUG'
        return @($Value)
    }

    Write-Log "Wrapping single Horizon response object of type '$($Value.GetType().FullName)' into a one-item array." 'DEBUG'
    return @($Value)
}

function Test-HorizonPaginationCompleteError {
    param(
        [AllowNull()]
        [string]$ErrorMessage
    )

    if ([string]::IsNullOrWhiteSpace($ErrorMessage)) {
        return $false
    }

    $isPaginationComplete = (
        $ErrorMessage -like 'HTTP 400 calling*' -and
        (
            $ErrorMessage -match 'common\.pagination\.records\.error' -or
            $ErrorMessage -match 'No More Records to paginate'
        )
    )

    if ($isPaginationComplete) {
        Write-Log 'Detected Horizon pagination-complete response pattern in HTTP 400 error payload.' 'DEBUG'
    }

    return $isPaginationComplete
}

function Get-HorizonConnectionServerMetadata {
    param(
        [Parameter(Mandatory = $true)]
        [string]$BrokerName,

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

    $uri = New-HorizonUri -BrokerName $BrokerName -Path 'rest/config/v2/connection-servers'
    Write-Log "Querying Horizon connection server metadata at $uri"

    try {
        $servers = ConvertTo-HorizonRecordArray -Value (Invoke-HorizonApiRequest -Method GET -Uri $uri -BearerToken $AccessToken)
    }
    catch {
        Write-Log "Unable to determine Horizon connection server version from $uri. $($_.Exception.Message)" 'WARN'
        return [pscustomobject]@{
            BrokerName          = $BrokerName
            MatchedServerName   = $null
            Version             = $null
            ApiProfile          = 'Unknown'
            DetectionSucceeded  = $false
        }
    }

    Write-Log "Connection server metadata query returned $($servers.Count) record(s)." 'DEBUG'
    if ($servers.Count -eq 0) {
        Write-Log 'Connection server metadata query returned no records.' 'WARN'
        return [pscustomobject]@{
            BrokerName          = $BrokerName
            MatchedServerName   = $null
            Version             = $null
            ApiProfile          = 'Unknown'
            DetectionSucceeded  = $false
        }
    }

    $normalizedBrokerName = $BrokerName.Trim().ToLowerInvariant()
    $matchedServer = $null

    foreach ($server in $servers) {
        $candidateHosts = New-Object System.Collections.Generic.List[string]

        foreach ($propertyName in @('name', 'fqhn')) {
            $propertyValue = [string]$server.$propertyName
            if (-not [string]::IsNullOrWhiteSpace($propertyValue)) {
                [void]$candidateHosts.Add($propertyValue.Trim().ToLowerInvariant())
            }
        }

        foreach ($propertyName in @('server_url', 'external_url')) {
            $propertyValue = [string]$server.$propertyName
            if ([string]::IsNullOrWhiteSpace($propertyValue)) {
                continue
            }

            try {
                $uriValue = [System.Uri]$propertyValue
                if (-not [string]::IsNullOrWhiteSpace($uriValue.Host)) {
                    [void]$candidateHosts.Add($uriValue.Host.Trim().ToLowerInvariant())
                }
            }
            catch {
                # Ignore malformed URL values from optional metadata fields.
            }
        }

        Write-Log "Evaluating connection server metadata candidate hosts: $($candidateHosts -join ', ')" 'DEBUG'
        if ($candidateHosts -contains $normalizedBrokerName) {
            $matchedServer = $server
            break
        }
    }

    if ($null -eq $matchedServer -and $servers.Count -eq 1) {
        $matchedServer = $servers[0]
    }

    $version = $null
    $matchedServerName = $null
    if ($null -ne $matchedServer) {
        $version = [string]$matchedServer.version
        $matchedServerName = [string]$matchedServer.name
    }

    $apiProfile = 'Default'
    if (-not [string]::IsNullOrWhiteSpace($version) -and $version -match '^8\.4(\.|-)') {
        $apiProfile = 'Horizon2111'
    }
    elseif ([string]::IsNullOrWhiteSpace($version)) {
        $apiProfile = 'Unknown'
    }

    if (-not [string]::IsNullOrWhiteSpace($version)) {
        Write-Log "Detected Horizon connection server version '$version' for '$BrokerName'. Using API profile '$apiProfile'."
    }
    else {
        Write-Log "Connection server metadata did not include a version for '$BrokerName'. Continuing with default API behavior." 'WARN'
    }

    return [pscustomobject]@{
        BrokerName          = $BrokerName
        MatchedServerName   = $matchedServerName
        Version             = $version
        ApiProfile          = $apiProfile
        DetectionSucceeded  = ($null -ne $matchedServer)
    }
}

function Invoke-HorizonApiRequest {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('GET', 'POST')]
        [string]$Method,

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

        [string]$BearerToken,

        [object]$Body
    )

    $hasBearerToken = -not [string]::IsNullOrWhiteSpace($BearerToken)
    Write-Log "Preparing $Method request to '$Uri'. AuthorizationHeaderPresent=$hasBearerToken. BodyIncluded=$($PSBoundParameters.ContainsKey('Body'))." 'DEBUG'
    if ($PSBoundParameters.ContainsKey('Body') -and $Body -is [hashtable]) {
        Write-Log "Request body keys: $($Body.Keys -join ', ')" 'DEBUG'
    }

    $request = [System.Net.HttpWebRequest]::Create($Uri)
    $request.Method = $Method
    $request.Timeout = 30000
    $request.ReadWriteTimeout = 30000
    $request.AllowAutoRedirect = $true
    $request.UserAgent = 'Get-HorizonBroker/1.0'
    $request.Accept = 'application/json'

    if (-not [string]::IsNullOrWhiteSpace($BearerToken)) {
        $request.Headers['Authorization'] = "Bearer $BearerToken"
    }

    if ($PSBoundParameters.ContainsKey('Body')) {
        $jsonBody = $Body | ConvertTo-Json -Depth 10 -Compress
        $bodyBytes = [System.Text.Encoding]::UTF8.GetBytes($jsonBody)
        Write-Log "Serialized request body length for '$Uri' is $($bodyBytes.Length) byte(s)." 'DEBUG'
        $request.ContentType = 'application/json'
        $request.ContentLength = $bodyBytes.Length

        $requestStream = $request.GetRequestStream()
        try {
            $requestStream.Write($bodyBytes, 0, $bodyBytes.Length)
        }
        finally {
            $requestStream.Dispose()
        }
    }

    try {
        $response = [System.Net.HttpWebResponse]$request.GetResponse()
        try {
            $reader = New-Object System.IO.StreamReader($response.GetResponseStream())
            try {
                $content = $reader.ReadToEnd()
            }
            finally {
                $reader.Dispose()
            }

            if ([string]::IsNullOrWhiteSpace($content)) {
                Write-Log "Received an empty response body from '$Uri'." 'DEBUG'
                return $null
            }

            Write-Log "Received response body from '$Uri' with $($content.Length) character(s)." 'DEBUG'
            return $content | ConvertFrom-Json
        }
        finally {
            $response.Close()
        }
    }
    catch [System.Net.WebException] {
        $message = $_.Exception.Message
        $response = $_.Exception.Response

        if ($response) {
            try {
                $reader = New-Object System.IO.StreamReader($response.GetResponseStream())
                try {
                    $errorBody = $reader.ReadToEnd()
                }
                finally {
                    $reader.Dispose()
                }

                $statusCode = [int]([System.Net.HttpWebResponse]$response).StatusCode
                if (-not [string]::IsNullOrWhiteSpace($errorBody)) {
                    throw "HTTP $statusCode calling '$Uri'. Response: $errorBody"
                }

                throw "HTTP $statusCode calling '$Uri'."
            }
            finally {
                $response.Close()
            }
        }

        throw "Request to '$Uri' failed. $message"
    }
}

function Test-BrokerEndpoint {
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$BrokerName
    )

    if ([string]::IsNullOrWhiteSpace($BrokerName)) {
        Write-Log 'Broker name is null or empty. Skipping web request.' 'WARN'
        return $false
    }

    $uri = New-HorizonUri -BrokerName $BrokerName -Path 'favicon.ico'
    Write-Log "Testing broker endpoint: $uri"

    foreach ($method in @('HEAD', 'GET')) {
        try {
            Write-Log "Attempting $method request to $uri"
            $request = [System.Net.HttpWebRequest]::Create($uri)
            $request.Method = $method
            $request.Timeout = 15000
            $request.ReadWriteTimeout = 15000
            $request.AllowAutoRedirect = $true
            $request.UserAgent = 'Get-HorizonBroker/1.0'

            $response = $request.GetResponse()

            try {
                $statusCode = [int]([System.Net.HttpWebResponse]$response).StatusCode
                if ($statusCode -ge 200 -and $statusCode -lt 400) {
                    Write-Log "Endpoint responded successfully with status code $statusCode using $method." 'SUCCESS'
                    return $true
                }

                Write-Log "Endpoint returned unexpected status code $statusCode using $method." 'WARN'
            }
            finally {
                $response.Close()
            }
        }
        catch [System.Net.WebException] {
            $webResponse = $_.Exception.Response
            if ($webResponse) {
                try {
                    $statusCode = [int]([System.Net.HttpWebResponse]$webResponse).StatusCode
                    Write-Log "Request to '$uri' returned HTTP status code $statusCode using $method." 'WARN'
                }
                finally {
                    $webResponse.Close()
                }
            }
            else {
                Write-Log "Request to '$uri' failed during $method. $($_.Exception.Message)" 'WARN'
            }
        }
        catch {
            Write-Log "Request to '$uri' failed during $method. $($_.Exception.Message)" 'WARN'
        }
    }

    return $false
}

function Select-ResponsiveBroker {
    param(
        [AllowNull()]
        [AllowEmptyCollection()]
        [string[]]$BrokerNames,

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

    if ($null -eq $BrokerNames -or $BrokerNames.Count -eq 0) {
        Write-Log "No broker candidates were available from $SourceLabel." 'WARN'
        return $null
    }

    Write-Log "Evaluating $($BrokerNames.Count) broker candidate(s) from $SourceLabel." 'DEBUG'
    foreach ($brokerName in @($BrokerNames | Select-Object -Unique)) {
        Write-Log "Testing broker candidate '$brokerName' from $SourceLabel."
        if (Test-BrokerEndpoint -BrokerName $brokerName) {
            Write-Log "Selected broker '$brokerName' from $SourceLabel." 'SUCCESS'
            return $brokerName
        }
    }

    Write-Log "No broker candidates from $SourceLabel responded successfully." 'WARN'
    return $null
}

function Get-LocalHostIdentifiers {
    $identifiers = New-Object System.Collections.Generic.List[string]

    if (-not [string]::IsNullOrWhiteSpace($env:COMPUTERNAME)) {
        [void]$identifiers.Add($env:COMPUTERNAME.ToLowerInvariant())
        Write-Log "Added hostname identifier from COMPUTERNAME: $($env:COMPUTERNAME.ToLowerInvariant())" 'DEBUG'
    }

    try {
        $fqdn = [System.Net.Dns]::GetHostEntry($env:COMPUTERNAME).HostName
        if (-not [string]::IsNullOrWhiteSpace($fqdn)) {
            [void]$identifiers.Add($fqdn.ToLowerInvariant())
            Write-Log "Added hostname identifier from DNS lookup: $($fqdn.ToLowerInvariant())" 'DEBUG'
        }
    }
    catch {
        Write-Log "Unable to resolve local FQDN from hostname '$($env:COMPUTERNAME)'. $($_.Exception.Message)" 'WARN'
    }

    try {
        $cs = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
        if (-not [string]::IsNullOrWhiteSpace($cs.DNSHostName)) {
            [void]$identifiers.Add($cs.DNSHostName.ToLowerInvariant())
            Write-Log "Added hostname identifier from Win32_ComputerSystem DNSHostName: $($cs.DNSHostName.ToLowerInvariant())" 'DEBUG'
        }

        if (-not [string]::IsNullOrWhiteSpace($cs.Domain) -and -not [string]::IsNullOrWhiteSpace($cs.DNSHostName)) {
            [void]$identifiers.Add(("$($cs.DNSHostName).$($cs.Domain)").ToLowerInvariant())
            Write-Log "Added hostname identifier from Win32_ComputerSystem FQDN: $(("$($cs.DNSHostName).$($cs.Domain)").ToLowerInvariant())" 'DEBUG'
        }
    }
    catch {
        Write-Log "Unable to query Win32_ComputerSystem for additional hostname variants. $($_.Exception.Message)" 'WARN'
    }

    $uniqueIdentifiers = $identifiers | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique
    Write-Log "Local host identifiers: $($uniqueIdentifiers -join ', ')"
    return $uniqueIdentifiers
}

function Get-LocalOperatingSystemRole {
    try {
        $os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
        $productType = [int]$os.ProductType

        switch ($productType) {
            1 { $role = 'Workstation' }
            2 { $role = 'DomainController' }
            3 { $role = 'Server' }
            default { $role = 'Unknown' }
        }

        Write-Log "Win32_OperatingSystem returned Caption='$($os.Caption)', Version='$($os.Version)', ProductType=$productType." 'DEBUG'
        Write-Log "Local operating system role detected as '$role' (ProductType=$productType)."
        return $role
    }
    catch {
        Write-Log "Unable to determine local operating system role. $($_.Exception.Message)" 'WARN'
        return 'Unknown'
    }
}

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

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

    $filter = @{ type = 'Equals'; name = $FieldName; value = $Value } | ConvertTo-Json -Compress
    Write-Log "Built Horizon filter for field '$FieldName' and value '$Value': $filter" 'DEBUG'
    return $filter
}

function Get-MachineFieldValue {
    param(
        [Parameter(Mandatory = $true)]
        [object]$Machine,

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

    switch ($FieldName) {
        'dns_name' { return $Machine.dns_name }
        'name' { return $Machine.name }
        'managed_machine_data.host_name' { return $Machine.managed_machine_data.host_name }
        default { return $null }
    }
}

function Find-LocalMachineMatch {
    param(
        [AllowNull()]
        [AllowEmptyCollection()]
        [array]$Machines,

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

    if ($null -eq $Machines -or $Machines.Count -eq 0) {
        return $null
    }

    Write-Log "Attempting to match $($Machines.Count) Horizon record(s) against identifiers: $($Identifiers -join ', ')" 'DEBUG'
    foreach ($machine in $Machines) {
        foreach ($fieldName in @('dns_name', 'name', 'managed_machine_data.host_name')) {
            $fieldValue = Get-MachineFieldValue -Machine $machine -FieldName $fieldName
            if ([string]::IsNullOrWhiteSpace($fieldValue)) {
                continue
            }

            $normalizedFieldValue = $fieldValue.ToLowerInvariant()
            if ($Identifiers -contains $normalizedFieldValue) {
                Write-Log "Matched local machine using $fieldName='$fieldValue'."
                return $machine
            }
        }
    }

    Write-Log 'No Horizon inventory record matched the current identifier set in this pass.' 'DEBUG'
    return $null
}

function Get-HorizonInventoryRecord {
    param(
        [Parameter(Mandatory = $true)]
        [string]$BrokerName,

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

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

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

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

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

        [switch]$SkipPagedScan,

        [int]$PageSize = 200,

        [int]$MaxPages = 1000
    )

    Write-Log "Starting inventory lookup for Horizon $RecordTypeLabel records on endpoint '$EndpointPath'. SkipPagedScan=$($SkipPagedScan.IsPresent). PageSize=$PageSize. MaxPages=$MaxPages." 'DEBUG'
    $filterRejectedByServer = $false

    foreach ($identifier in $Identifiers) {
        foreach ($fieldName in $FieldNames) {
            $filter = New-HorizonFilter -FieldName $fieldName -Value $identifier
            $uri = New-HorizonUri -BrokerName $BrokerName -Path "rest/$EndpointPath" -QueryParameters @{
                size   = 50
                filter = $filter
            }

            Write-Log "Querying Horizon $RecordTypeLabel inventory using $fieldName='$identifier'."
            try {
                $records = Invoke-HorizonApiRequest -Method GET -Uri $uri -BearerToken $AccessToken
            }
            catch {
                $requestError = $_.Exception.Message
                if ($requestError -like 'HTTP 400 calling*') {
                    Write-Log "Horizon rejected the filtered $RecordTypeLabel inventory query for $fieldName='$identifier'. Falling back to unfiltered paged inventory scan. $requestError" 'WARN'
                    $filterRejectedByServer = $true
                    break
                }

                throw
            }

            $records = ConvertTo-HorizonRecordArray -Value $records
            Write-Log "Filtered Horizon $RecordTypeLabel query for $fieldName='$identifier' returned $($records.Count) record(s)." 'DEBUG'

            $record = Find-LocalMachineMatch -Machines $records -Identifiers $Identifiers
            if ($null -ne $record) {
                Write-Log "Matched Horizon $RecordTypeLabel '$($record.name)' with state '$($record.state)'." 'SUCCESS'
                return $record
            }
        }

        if ($filterRejectedByServer) {
            break
        }
    }

    if ($SkipPagedScan) {
        Write-Log "Filtered Horizon $RecordTypeLabel lookup completed without a match and paged scanning is disabled for this pass." 'DEBUG'
        return $null
    }

    for ($page = 1; $page -le $MaxPages; $page++) {
        $uri = New-HorizonUri -BrokerName $BrokerName -Path "rest/$EndpointPath" -QueryParameters @{
            page = $page
            size = $PageSize
        }
        Write-Log "Falling back to paged $RecordTypeLabel inventory scan. Querying page $page."
        try {
            $records = Invoke-HorizonApiRequest -Method GET -Uri $uri -BearerToken $AccessToken
        }
        catch {
            $requestError = $_.Exception.Message
            if (Test-HorizonPaginationCompleteError -ErrorMessage $requestError) {
                Write-Log "Horizon reported there are no more $RecordTypeLabel records to paginate after page $($page - 1)." 'INFO'
                break
            }

            throw
        }

        $records = ConvertTo-HorizonRecordArray -Value $records
        Write-Log "Paged Horizon $RecordTypeLabel query page $page returned $($records.Count) record(s)." 'DEBUG'

        if ($records.Count -eq 0) {
            break
        }

        $record = Find-LocalMachineMatch -Machines $records -Identifiers $Identifiers
        if ($null -ne $record) {
            Write-Log "Matched Horizon $RecordTypeLabel '$($record.name)' with state '$($record.state)' during paged scan." 'SUCCESS'
            return $record
        }

        if ($records.Count -lt $PageSize) {
            Write-Log "Reached the final $RecordTypeLabel inventory page at page $page because only $($records.Count) record(s) were returned."
            break
        }
    }

    if ($page -gt $MaxPages) {
        Write-Log "Stopped paged $RecordTypeLabel inventory scan after reaching the maximum page limit of $MaxPages." 'WARN'
    }

    Write-Log "No Horizon $RecordTypeLabel record matched any local identifier after all lookup strategies completed." 'DEBUG'
    return $null
}

function Get-HorizonAccessToken {
    param(
        [Parameter(Mandatory = $true)]
        [string]$BrokerName,

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

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

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

    $uri = New-HorizonUri -BrokerName $BrokerName -Path 'rest/login'
    $passwordFormats = @(
        @{
            Label = 'plain string'
            Value = $Password
        },
        @{
            Label = 'character array'
            Value = @($Password.ToCharArray() | ForEach-Object { [string]$_ })
        }
    )

    Write-Log "Preparing Horizon login request using $($passwordFormats.Count) password serialization format(s)." 'DEBUG'
    Write-Log "Authenticating to Horizon REST API at $uri as '$Domain\$Username'."

    $lastError = $null
    foreach ($passwordFormat in $passwordFormats) {
        try {
            Write-Log "Trying login with password format: $($passwordFormat.Label)."
            $body = @{
                domain   = $Domain
                username = $Username
                password = $passwordFormat.Value
            }

            $response = Invoke-HorizonApiRequest -Method POST -Uri $uri -Body $body

            if ([string]::IsNullOrWhiteSpace($response.access_token)) {
                throw 'Login response did not contain an access_token.'
            }

            Write-Log "Horizon API authentication succeeded using password format: $($passwordFormat.Label)." 'SUCCESS'
            return $response.access_token
        }
        catch {
            $lastError = $_.Exception.Message
            Write-Log "Login attempt using password format '$($passwordFormat.Label)' failed. $lastError" 'WARN'
        }
    }

    throw "Horizon API authentication failed. $lastError"
}

function Get-HorizonMachineStatus {
    param(
        [Parameter(Mandatory = $true)]
        [string]$BrokerName,

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

    $identifiers = Get-LocalHostIdentifiers
    $operatingSystemRole = Get-LocalOperatingSystemRole
    $inventoryTargets = @()

    if ($operatingSystemRole -eq 'Workstation') {
        $inventoryTargets += [pscustomobject]@{
            EndpointPath     = 'inventory/v1/machines'
            FieldNames       = @('dns_name', 'name')
            RecordTypeLabel  = 'machine'
            RecordTypeValue  = 'Machine'
            SupportsPairing  = $true
        }
        $inventoryTargets += [pscustomobject]@{
            EndpointPath     = 'inventory/v1/rds-servers'
            FieldNames       = @('dns_name', 'name')
            RecordTypeLabel  = 'RDS server'
            RecordTypeValue  = 'RDS Server'
            SupportsPairing  = $false
        }
    }
    else {
        $inventoryTargets += [pscustomobject]@{
            EndpointPath     = 'inventory/v1/rds-servers'
            FieldNames       = @('dns_name', 'name')
            RecordTypeLabel  = 'RDS server'
            RecordTypeValue  = 'RDS Server'
            SupportsPairing  = $false
        }
        $inventoryTargets += [pscustomobject]@{
            EndpointPath     = 'inventory/v1/machines'
            FieldNames       = @('dns_name', 'name')
            RecordTypeLabel  = 'machine'
            RecordTypeValue  = 'Machine'
            SupportsPairing  = $true
        }
    }

    Write-Log "Inventory lookup order for local role '$operatingSystemRole': $((@($inventoryTargets | ForEach-Object { $_.RecordTypeLabel })) -join ' -> ')." 'DEBUG'
    foreach ($inventoryTarget in $inventoryTargets) {
        $record = Get-HorizonInventoryRecord `
            -BrokerName $BrokerName `
            -AccessToken $AccessToken `
            -EndpointPath $inventoryTarget.EndpointPath `
            -Identifiers $identifiers `
            -FieldNames $inventoryTarget.FieldNames `
            -RecordTypeLabel $inventoryTarget.RecordTypeLabel `
            -SkipPagedScan

        if ($null -eq $record) {
            continue
        }

        if (-not $inventoryTarget.SupportsPairing -and -not ($record.PSObject.Properties.Name -contains 'pairing_state')) {
            Add-Member -InputObject $record -NotePropertyName pairing_state -NotePropertyValue $null -Force
        }

        Add-Member -InputObject $record -NotePropertyName record_type -NotePropertyValue $inventoryTarget.RecordTypeValue -Force
        Write-Log "Matched Horizon $($inventoryTarget.RecordTypeLabel) '$($record.name)' with state '$($record.state)' during filtered lookup." 'SUCCESS'
        return $record
    }

    foreach ($inventoryTarget in $inventoryTargets) {
        $record = Get-HorizonInventoryRecord `
            -BrokerName $BrokerName `
            -AccessToken $AccessToken `
            -EndpointPath $inventoryTarget.EndpointPath `
            -Identifiers $identifiers `
            -FieldNames $inventoryTarget.FieldNames `
            -RecordTypeLabel $inventoryTarget.RecordTypeLabel

        if ($null -eq $record) {
            continue
        }

        if (-not $inventoryTarget.SupportsPairing -and -not ($record.PSObject.Properties.Name -contains 'pairing_state')) {
            Add-Member -InputObject $record -NotePropertyName pairing_state -NotePropertyValue $null -Force
        }

        Add-Member -InputObject $record -NotePropertyName record_type -NotePropertyValue $inventoryTarget.RecordTypeValue -Force
        Write-Log "Matched Horizon $($inventoryTarget.RecordTypeLabel) '$($record.name)' with state '$($record.state)' after paged lookup." 'SUCCESS'
        return $record
    }

    throw 'No Horizon machine or RDS server record matched the local host name or FQDN.'
}

function Format-DisplayValue {
    param(
        [AllowNull()]
        [object]$Value
    )

    if ($null -eq $Value) {
        return 'NULL'
    }

    if ($Value -is [System.Array]) {
        $values = @(
            $Value |
                ForEach-Object { [string]$_ } |
                Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
        )

        if ($values.Count -eq 0) {
            return 'NULL'
        }

        return $values -join ', '
    }

    $stringValue = [string]$Value
    if ([string]::IsNullOrWhiteSpace($stringValue)) {
        return 'NULL'
    }

    return $stringValue
}

$script:exitCode = 1
$OmnissaBrokers = @()
$VMwareBrokers = @()
$Broker = $null
$ConfirmedBroker = $null
$ConnectionServerVersion = $null
$AgentStatus = $null
$PairingState = $null
$RecordType = $null

try {
    Write-Log 'Starting Horizon broker discovery.'
    Write-Log "Script path resolved to '$script:ScriptPath'." 'DEBUG'
    Write-Log "Log file for this run: '$script:LogFilePath'. MinimumLogLevel='$script:MinimumLogLevel'." 'DEBUG'
    Write-Log "Operations summary log path: '$script:OperationsLogPath'." 'DEBUG'
    Write-Log "Credential availability: DomainProvided=$(-not [string]::IsNullOrWhiteSpace($Domain)); UsernameProvided=$(-not [string]::IsNullOrWhiteSpace($Username)); PasswordProvided=$(-not [string]::IsNullOrWhiteSpace($Password))." 'DEBUG'
    Enable-InsecureTlsValidation

    $OmnissaBrokers = Get-RegistryBrokerValues -Path 'HKLM:\SOFTWARE\Omnissa\Horizon\Agent\Configuration' -Name 'Broker'
    $VMwareBrokers = Get-RegistryBrokerValues -Path 'HKLM:\SOFTWARE\VMware, Inc.\VMware VDM\Agent\Configuration' -Name 'Broker'
    Write-Log "Loaded $($OmnissaBrokers.Count) Omnissa broker candidate(s) and $($VMwareBrokers.Count) VMware broker candidate(s)." 'DEBUG'

    if ($OmnissaBrokers.Count -eq 0) {
        Write-Log 'No Omnissa broker values were found. Setting $OmnissaBrokers to NULL.' 'WARN'
    }

    if ($VMwareBrokers.Count -eq 0) {
        Write-Log 'No VMware broker values were found. Setting $VMwareBrokers to NULL.' 'WARN'
    }

    $Broker = Select-ResponsiveBroker -BrokerNames $OmnissaBrokers -SourceLabel 'Omnissa registry value'
    if (-not [string]::IsNullOrWhiteSpace($Broker)) {
        $ConfirmedBroker = $Broker
        $script:exitCode = 0
    }
    else {
        $Broker = Select-ResponsiveBroker -BrokerNames $VMwareBrokers -SourceLabel 'VMware registry value'
    }

    if ($script:exitCode -ne 0 -and -not [string]::IsNullOrWhiteSpace($Broker)) {
        $ConfirmedBroker = $Broker
        $script:exitCode = 0
    }

    if ($script:exitCode -ne 0) {
        Write-Log 'No broker endpoint responded successfully.' 'ERROR'
    }

    if ($script:exitCode -eq 0) {
        if (
            -not [string]::IsNullOrWhiteSpace($Domain) -and
            -not [string]::IsNullOrWhiteSpace($Username) -and
            -not [string]::IsNullOrWhiteSpace($Password)
        ) {
            $accessToken = Get-HorizonAccessToken -BrokerName $Broker -Domain $Domain -Username $Username -Password $Password
            $connectionServerMetadata = Get-HorizonConnectionServerMetadata -BrokerName $Broker -AccessToken $accessToken
            $ConnectionServerVersion = $connectionServerMetadata.Version
            Write-Log "Connection server metadata result: MatchedServerName='$(Format-DisplayValue -Value $connectionServerMetadata.MatchedServerName)', ApiProfile='$(Format-DisplayValue -Value $connectionServerMetadata.ApiProfile)', DetectionSucceeded='$($connectionServerMetadata.DetectionSucceeded)'." 'DEBUG'
            $machine = Get-HorizonMachineStatus -BrokerName $Broker -AccessToken $accessToken
            $RecordType = $machine.record_type
            $AgentStatus = $machine.state
            $PairingState = $machine.pairing_state

            if ($RecordType -eq 'RDS Server' -and [string]::IsNullOrWhiteSpace($PairingState)) {
                $PairingState = 'Pairing state is not exposed for RDS server records'
            }

            Write-Log "Agent status for this machine is '$AgentStatus'. Pairing state is '$PairingState'." 'SUCCESS'
        }
        else {
            Write-Log 'Broker discovery succeeded, but Username, Domain, or Password is blank at the top of the script. Skipping agent status query.' 'WARN'
        }
    }

    Write-Log "Final values: OmnissaBroker='$(Format-DisplayValue -Value $OmnissaBrokers)', VMwareBroker='$(Format-DisplayValue -Value $VMwareBrokers)', ConfirmedBroker='$(Format-DisplayValue -Value $ConfirmedBroker)', ConnectionServerVersion='$(Format-DisplayValue -Value $ConnectionServerVersion)', AgentStatus='$(Format-DisplayValue -Value $AgentStatus)', PairingState='$(Format-DisplayValue -Value $PairingState)'"
}
catch {
    Write-Log "Unexpected fatal error. $($_.Exception.Message)" 'ERROR'
    $script:exitCode = 1
}
finally {
    Write-OperationsSummaryLog -ConnectionServerName (Format-DisplayValue -Value $ConfirmedBroker) -AgentStatusValue (Format-DisplayValue -Value $AgentStatus)
    Write-Output "ConfirmedBroker=$(Format-DisplayValue -Value $ConfirmedBroker)"
    Write-Output "ConnectionServerVersion=$(Format-DisplayValue -Value $ConnectionServerVersion)"
    Write-Output "AgentStatus=$(Format-DisplayValue -Value $AgentStatus)"
    Write-Output "PairingState=$(Format-DisplayValue -Value $PairingState)"
    Write-Output "LogFile=$(Format-DisplayValue -Value $script:LogFilePath)"
    Write-Log "Exiting with code $script:exitCode."
    exit $script:exitCode
}
