param(
    [string]$OutputPath,

    [string]$PackageName = "UnifiedImaging.ImageAnywhere",

    [string]$PackageFamilyName = "UnifiedImaging.ImageAnywhere_ytf96cz97jc64",

    [string]$AppUserModelId = "UnifiedImaging.ImageAnywhere_ytf96cz97jc64!App",

    [string]$AgentInstallDirectory = (Join-Path $env:LOCALAPPDATA "Programs\ImageAnywhereAgent"),

    [string]$AgentTaskName = "ImageAnywhereAgent",

    [string]$CandidatePackagePath,

    [string]$MachineAlias,

    [ValidateSet("Any", "Shadow", "Active")]
    [string]$ExpectedSupervisionMode = "Shadow",

    [int]$MaxClientHeartbeatAgeSeconds = 120,

    [int]$MaxAgentHeartbeatAgeSeconds = 600,

    [switch]$RequireSupervisionEnabled,

    [switch]$RequireAgentTask
)

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

$checks = New-Object System.Collections.Generic.List[object]

function Add-Check {
    param(
        [string]$Name,
        [ValidateSet("Pass", "Warn", "Fail")]
        [string]$Status,
        [string]$Code
    )

    $checks.Add([ordered]@{
        name = $Name
        status = $Status
        code = $Code
    })
}

function Get-OptionalProperty {
    param(
        [object]$Object,
        [string]$Name
    )

    if ($null -eq $Object) {
        return $null
    }

    $property = $Object.PSObject.Properties[$Name]
    if ($null -eq $property) {
        return $null
    }

    return $property.Value
}

function Read-JsonFileSafe {
    param([string]$Path)

    $result = [ordered]@{
        exists = $false
        parsed = $false
        value = $null
        code = "missing"
    }

    if ([String]::IsNullOrWhiteSpace($Path) -or -not (Test-Path -LiteralPath $Path -PathType Leaf)) {
        return $result
    }

    $result.exists = $true
    try {
        $result.value = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
        $result.parsed = $null -ne $result.value
        $result.code = if ($result.parsed) { "parsed" } else { "empty" }
    }
    catch {
        $result.code = "invalid-json"
    }

    return $result
}

function Get-AgeSeconds {
    param([object]$Value)

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

    $captured = [DateTimeOffset]::MinValue
    if (-not [DateTimeOffset]::TryParse([string]$Value, [ref]$captured)) {
        return $null
    }

    return [Math]::Round(([DateTimeOffset]::UtcNow - $captured.ToUniversalTime()).TotalSeconds, 1)
}

function Test-EnumValue {
    param(
        [object]$Value,
        [string[]]$Allowed
    )

    return $null -ne $Value -and $Allowed -contains [string]$Value
}

function Get-TaskObservation {
    param(
        [string]$TaskName,
        [string]$ExpectedAgentPath
    )

    $observation = [ordered]@{
        exists = $false
        actionMatchesAgent = $false
        state = $null
        code = "task-not-found"
    }

    try {
        $previousErrorAction = $ErrorActionPreference
        $ErrorActionPreference = "Continue"
        $xmlOutput = & schtasks.exe /Query /TN $TaskName /XML 2>&1
        $xmlExitCode = $LASTEXITCODE
        $ErrorActionPreference = $previousErrorAction
        $xmlText = (@($xmlOutput) -join [Environment]::NewLine).Trim()
        if ($xmlExitCode -ne 0 -or [String]::IsNullOrWhiteSpace($xmlText)) {
            return $observation
        }

        [xml]$taskXml = $xmlText
        $command = [string]$taskXml.Task.Actions.Exec.Command
        $arguments = [string]$taskXml.Task.Actions.Exec.Arguments
        $observation.exists = $true
        if (-not [String]::IsNullOrWhiteSpace($command)) {
            $expandedCommand = [Environment]::ExpandEnvironmentVariables($command.Trim('"'))
            $observation.actionMatchesAgent = [String]::Equals(
                $expandedCommand,
                $ExpectedAgentPath,
                [StringComparison]::OrdinalIgnoreCase)

            $isWscript = [IO.Path]::GetFileName($expandedCommand) -in @("wscript.exe", "wscript")
            if (-not $observation.actionMatchesAgent -and $isWscript -and -not [String]::IsNullOrWhiteSpace($arguments)) {
                $launcherMatch = [regex]::Match($arguments, '"(?<path>[^"]+\.vbs)"', [Text.RegularExpressions.RegexOptions]::IgnoreCase)
                if ($launcherMatch.Success) {
                    $launcherPath = [Environment]::ExpandEnvironmentVariables($launcherMatch.Groups["path"].Value)
                    if (Test-Path -LiteralPath $launcherPath -PathType Leaf) {
                        $launcherText = Get-Content -LiteralPath $launcherPath -Raw
                        $observation.actionMatchesAgent = $launcherText -match [regex]::Escape($ExpectedAgentPath)
                    }
                }
            }
        }

        $ErrorActionPreference = "Continue"
        $listOutput = & schtasks.exe /Query /TN $TaskName /FO LIST /V 2>&1
        $listExitCode = $LASTEXITCODE
        $ErrorActionPreference = $previousErrorAction
        $listText = (@($listOutput) -join [Environment]::NewLine).Trim()
        if ($listExitCode -eq 0 -and $listText -match "(?im)^Status:\s*(.+)$") {
            $observation.state = $Matches[1].Trim()
        }
        $observation.code = if ($observation.actionMatchesAgent) {
            "task-action-matches"
        }
        else {
            "task-action-mismatch"
        }
    }
    catch {
        if ($null -ne $previousErrorAction) {
            $ErrorActionPreference = $previousErrorAction
        }
        $observation.code = "task-query-failed"
    }

    return $observation
}

function Get-AuthCategory {
    param([object]$AuthStatus)

    $value = [string]$AuthStatus
    if ([String]::IsNullOrWhiteSpace($value)) {
        return "Unknown"
    }
    if ($value -match "(?i)auth=paired") {
        return "Paired"
    }
    if ($value -match "(?i)auth=unpaired") {
        return "Unpaired"
    }
    if ($value -match "(?i)rejected|invalid_grant|sign-in") {
        return "AttentionRequired"
    }
    return "Unknown"
}

$agentExePath = Join-Path $AgentInstallDirectory "agent\IAUWP.Agent.exe"
$defaultDataRoot = Join-Path $env:LOCALAPPDATA "ImageAnywhere\agent"
$settingsPath = Join-Path $defaultDataRoot "agent-settings.json"
$settingsRead = Read-JsonFileSafe -Path $settingsPath
$agentDataRoot = $defaultDataRoot
if ($settingsRead.parsed) {
    $configuredDataRoot = Get-OptionalProperty -Object $settingsRead.value -Name "DataRootPath"
    if (-not [String]::IsNullOrWhiteSpace([string]$configuredDataRoot)) {
        $agentDataRoot = [Environment]::ExpandEnvironmentVariables([string]$configuredDataRoot)
        $settingsPath = Join-Path $agentDataRoot "agent-settings.json"
        if (-not [String]::Equals($agentDataRoot, $defaultDataRoot, [StringComparison]::OrdinalIgnoreCase)) {
            $settingsRead = Read-JsonFileSafe -Path $settingsPath
        }
    }
}

$package = $null
try {
    $package = Get-AppxPackage -Name $PackageName -ErrorAction SilentlyContinue |
        Sort-Object Version -Descending |
        Select-Object -First 1
}
catch {
}

$packageInstalled = $null -ne $package
$packageFamilyMatches = $packageInstalled -and [String]::Equals(
    [string]$package.PackageFamilyName,
    $PackageFamilyName,
    [StringComparison]::OrdinalIgnoreCase)
$packageVersion = if ($packageInstalled) { [string]$package.Version } else { $null }
$packageFullName = if ($packageInstalled) { [string]$package.PackageFullName } else { $null }
$registeredAumid = $false
try {
    $registeredAumid = @(
        Get-StartApps -ErrorAction SilentlyContinue |
            Where-Object { [String]::Equals([string]$_.AppID, $AppUserModelId, [StringComparison]::OrdinalIgnoreCase) }
    ).Count -gt 0
}
catch {
}
$localStatePath = Join-Path (Join-Path $env:LOCALAPPDATA "Packages") (Join-Path $PackageFamilyName "LocalState")
$clientStatusPath = Join-Path $localStatePath "unified-imaging\client-status\client-status.json"
$mirroredAgentStatusPath = Join-Path $localStatePath "unified-imaging\agent-status\agent-status.json"

Add-Check -Name "app-installed" -Status $(if ($packageInstalled) { "Pass" } else { "Fail" }) -Code $(if ($packageInstalled) { "package-installed" } else { "package-missing" })
Add-Check -Name "app-package-family" -Status $(if ($packageFamilyMatches) { "Pass" } else { "Fail" }) -Code $(if ($packageFamilyMatches) { "package-family-matches" } else { "package-family-mismatch" })
Add-Check -Name "app-aumid-registered" -Status $(if ($registeredAumid) { "Pass" } else { "Fail" }) -Code $(if ($registeredAumid) { "aumid-registered" } else { "aumid-not-registered" })

$clientRead = Read-JsonFileSafe -Path $clientStatusPath
$client = $clientRead.value
$clientSchema = Get-OptionalProperty -Object $client -Name "SchemaVersion"
$clientCapturedAt = Get-OptionalProperty -Object $client -Name "CapturedAtUtc"
$clientAgeSeconds = Get-AgeSeconds -Value $clientCapturedAt
$clientPackageVersion = Get-OptionalProperty -Object $client -Name "PackageVersion"
$clientStatusCode = Get-OptionalProperty -Object $client -Name "StatusCode"
$clientSubjectCount = Get-OptionalProperty -Object $client -Name "PendingTrialSubjectCount"
$clientImageCount = Get-OptionalProperty -Object $client -Name "PendingTrialImageCount"

Add-Check -Name "client-health-file" -Status $(if ($clientRead.parsed) { "Pass" } else { "Fail" }) -Code $clientRead.code
Add-Check -Name "client-health-schema" -Status $(if ($clientSchema -eq "1") { "Pass" } else { "Fail" }) -Code $(if ($clientSchema -eq "1") { "client-schema-supported" } else { "client-schema-unsupported" })
$clientFresh = $null -ne $clientAgeSeconds -and $clientAgeSeconds -ge 0 -and $clientAgeSeconds -le $MaxClientHeartbeatAgeSeconds
Add-Check -Name "client-health-fresh" -Status $(if ($clientFresh) { "Pass" } else { "Fail" }) -Code $(if ($clientFresh) { "client-heartbeat-fresh" } else { "client-heartbeat-missing-or-stale" })
$clientVersionMatches = -not [String]::IsNullOrWhiteSpace([string]$clientPackageVersion) -and [String]::Equals([string]$clientPackageVersion, $packageVersion, [StringComparison]::OrdinalIgnoreCase)
Add-Check -Name "client-package-version" -Status $(if ($clientVersionMatches) { "Pass" } else { "Fail" }) -Code $(if ($clientVersionMatches) { "client-version-matches" } else { "client-version-mismatch" })

$allowedClientKeys = @(
    "SchemaVersion", "CapturedAtUtc", "ProcessId", "PackageVersion",
    "ActivationSource", "LifecycleState", "AuthState", "RuntimeState",
    "NetworkState", "WatchFolderState", "PendingTrialSubjectCount",
    "PendingTrialImageCount", "LastPendingTrialDrainUtc", "StatusCode"
)
$clientKeys = @()
if ($null -ne $client) {
    $clientKeys = @($client.PSObject.Properties.Name)
}
$unexpectedClientKeyCount = @($clientKeys | Where-Object { $_ -notin $allowedClientKeys }).Count
$missingClientKeyCount = @($allowedClientKeys | Where-Object { $_ -notin $clientKeys }).Count
$clientBounded = $clientRead.parsed -and
    $missingClientKeyCount -eq 0 -and
    (Test-EnumValue $clientStatusCode @("Unknown", "Starting", "WaitingForNetwork", "SignInRequired", "Authenticated", "RuntimeReady", "RuntimeDegraded", "Suspending", "Resuming")) -and
    $null -ne $clientSubjectCount -and [int64]$clientSubjectCount -ge 0 -and
    $null -ne $clientImageCount -and [int64]$clientImageCount -ge 0
Add-Check -Name "client-health-bounded-contract" -Status $(if ($clientBounded) { "Pass" } else { "Fail" }) -Code $(if ($clientBounded) { "client-contract-bounded" } else { "client-contract-invalid" })
Add-Check -Name "client-health-phi-allowlist" -Status $(if ($unexpectedClientKeyCount -eq 0) { "Pass" } else { "Fail" }) -Code $(if ($unexpectedClientKeyCount -eq 0) { "client-allowlist-exact" } else { "client-unexpected-fields" })

$agentInstalled = Test-Path -LiteralPath $agentExePath -PathType Leaf
Add-Check -Name "agent-installed" -Status $(if ($agentInstalled) { "Pass" } else { "Fail" }) -Code $(if ($agentInstalled) { "agent-installed" } else { "agent-missing" })
$agentExecutableSha256 = if ($agentInstalled) {
    (Get-FileHash -LiteralPath $agentExePath -Algorithm SHA256).Hash
}
else {
    $null
}

$candidatePackagePresent = -not [String]::IsNullOrWhiteSpace($CandidatePackagePath) -and (Test-Path -LiteralPath $CandidatePackagePath -PathType Leaf)
$candidatePackageSha256 = if ($candidatePackagePresent) {
    (Get-FileHash -LiteralPath $CandidatePackagePath -Algorithm SHA256).Hash
}
else {
    $null
}
$candidatePackageSizeBytes = if ($candidatePackagePresent) {
    (Get-Item -LiteralPath $CandidatePackagePath).Length
}
else {
    $null
}
if (-not [String]::IsNullOrWhiteSpace($CandidatePackagePath)) {
    Add-Check -Name "candidate-package-file" -Status $(if ($candidatePackagePresent) { "Pass" } else { "Fail" }) -Code $(if ($candidatePackagePresent) { "candidate-package-hashed" } else { "candidate-package-missing" })
}

$agentProcesses = @(Get-Process -Name "IAUWP.Agent" -ErrorAction SilentlyContinue)
$agentMatchingProcessCount = 0
foreach ($process in $agentProcesses) {
    try {
        if ([String]::Equals($process.MainModule.FileName, $agentExePath, [StringComparison]::OrdinalIgnoreCase)) {
            $agentMatchingProcessCount++
        }
    }
    catch {
    }
}
Add-Check -Name "agent-running" -Status $(if ($agentMatchingProcessCount -eq 1) { "Pass" } elseif ($agentMatchingProcessCount -gt 1) { "Fail" } else { "Fail" }) -Code $(if ($agentMatchingProcessCount -eq 1) { "agent-single-instance-running" } elseif ($agentMatchingProcessCount -gt 1) { "agent-multiple-instances" } else { "agent-not-running" })

Add-Check -Name "agent-settings" -Status $(if ($settingsRead.parsed) { "Pass" } else { "Fail" }) -Code $settingsRead.code
$supervisionSettings = Get-OptionalProperty -Object $settingsRead.value -Name "ClientSupervision"
$supervisionEnabled = Get-OptionalProperty -Object $supervisionSettings -Name "Enabled"
$supervisionMode = Get-OptionalProperty -Object $supervisionSettings -Name "Mode"
$configuredAumid = Get-OptionalProperty -Object $supervisionSettings -Name "AppUserModelId"
$enabledStatus = if ($RequireSupervisionEnabled) {
    if ($supervisionEnabled -eq $true) { "Pass" } else { "Fail" }
}
else {
    if ($supervisionEnabled -eq $true) { "Pass" } else { "Warn" }
}
Add-Check -Name "supervision-enabled" -Status $enabledStatus -Code $(if ($supervisionEnabled -eq $true) { "supervision-enabled" } else { "supervision-disabled-default" })
$modeMatches = $ExpectedSupervisionMode -eq "Any" -or [String]::Equals([string]$supervisionMode, $ExpectedSupervisionMode, [StringComparison]::OrdinalIgnoreCase)
Add-Check -Name "supervision-mode" -Status $(if ($modeMatches) { "Pass" } else { "Fail" }) -Code $(if ($modeMatches) { "supervision-mode-matches" } else { "supervision-mode-mismatch" })
$aumidMatches = [String]::Equals([string]$configuredAumid, $AppUserModelId, [StringComparison]::OrdinalIgnoreCase)
Add-Check -Name "supervision-aumid" -Status $(if ($aumidMatches) { "Pass" } else { "Fail" }) -Code $(if ($aumidMatches) { "supervision-aumid-matches" } else { "supervision-aumid-mismatch" })

$agentStatusPath = Join-Path $agentDataRoot "status\agent-status.json"
$agentStatusRead = Read-JsonFileSafe -Path $agentStatusPath
if (-not $agentStatusRead.parsed) {
    $agentStatusRead = Read-JsonFileSafe -Path $mirroredAgentStatusPath
}
$agentStatus = $agentStatusRead.value
$agentSchema = Get-OptionalProperty -Object $agentStatus -Name "SchemaVersion"
$agentCapturedAt = Get-OptionalProperty -Object $agentStatus -Name "CapturedAtUtc"
$agentAgeSeconds = Get-AgeSeconds -Value $agentCapturedAt
$agentFresh = $null -ne $agentAgeSeconds -and $agentAgeSeconds -ge 0 -and $agentAgeSeconds -le $MaxAgentHeartbeatAgeSeconds
Add-Check -Name "agent-status-file" -Status $(if ($agentStatusRead.parsed) { "Pass" } else { "Fail" }) -Code $agentStatusRead.code
Add-Check -Name "agent-status-schema" -Status $(if ($agentSchema -eq "2") { "Pass" } else { "Fail" }) -Code $(if ($agentSchema -eq "2") { "agent-schema-supported" } else { "agent-schema-missing-supervision" })
Add-Check -Name "agent-status-fresh" -Status $(if ($agentFresh) { "Pass" } else { "Fail" }) -Code $(if ($agentFresh) { "agent-heartbeat-fresh" } else { "agent-heartbeat-missing-or-stale" })

$supervisionStatus = Get-OptionalProperty -Object $agentStatus -Name "ClientSupervision"
$supervisionState = Get-OptionalProperty -Object $supervisionStatus -Name "State"
$supervisionProposedAction = Get-OptionalProperty -Object $supervisionStatus -Name "ProposedAction"
$supervisionHealthReadState = Get-OptionalProperty -Object $supervisionStatus -Name "HealthReadState"
$packageCompatibility = Get-OptionalProperty -Object $supervisionStatus -Name "PackageCompatibility"
$maintenanceActive = Get-OptionalProperty -Object $supervisionStatus -Name "MaintenanceActive"
$circuitOpenUntilUtc = Get-OptionalProperty -Object $supervisionStatus -Name "CircuitOpenUntilUtc"
Add-Check -Name "supervision-status" -Status $(if ($null -ne $supervisionStatus) { "Pass" } else { "Fail" }) -Code $(if ($null -ne $supervisionStatus) { "supervision-status-present" } else { "supervision-status-missing" })
Add-Check -Name "supervision-package-compatible" -Status $(if ($packageCompatibility -eq "Compatible") { "Pass" } else { "Fail" }) -Code $(if ($packageCompatibility -eq "Compatible") { "supervision-package-compatible" } else { "supervision-package-not-compatible" })
Add-Check -Name "supervision-health-readable" -Status $(if ($supervisionHealthReadState -eq "Valid") { "Pass" } else { "Fail" }) -Code $(if ($supervisionHealthReadState -eq "Valid") { "supervision-health-valid" } else { "supervision-health-not-valid" })

$task = Get-TaskObservation -TaskName $AgentTaskName -ExpectedAgentPath $agentExePath
$taskStatus = if ($RequireAgentTask) {
    if ($task.exists -and $task.actionMatchesAgent) { "Pass" } else { "Fail" }
}
else {
    if ($task.exists -and $task.actionMatchesAgent) { "Pass" } else { "Warn" }
}
Add-Check -Name "agent-onlogon-task" -Status $taskStatus -Code $task.code

$failedCount = @($checks | Where-Object { $_.status -eq "Fail" }).Count
$warningCount = @($checks | Where-Object { $_.status -eq "Warn" }).Count
$passedCount = @($checks | Where-Object { $_.status -eq "Pass" }).Count
$authCategory = Get-AuthCategory -AuthStatus (Get-OptionalProperty -Object $agentStatus -Name "AuthStatus")
$checkArray = $checks.ToArray()

$result = [ordered]@{
    schemaVersion = "1"
    capturedAtUtc = [DateTimeOffset]::UtcNow.ToString("o")
    machineAlias = if ([String]::IsNullOrWhiteSpace($MachineAlias)) { $null } else { $MachineAlias }
    readOnly = $true
    ready = $failedCount -eq 0
    summary = [ordered]@{
        passedChecks = $passedCount
        warningChecks = $warningCount
        failedChecks = $failedCount
    }
    expected = [ordered]@{
        packageName = $PackageName
        packageFamilyName = $PackageFamilyName
        appUserModelId = $AppUserModelId
        supervisionMode = $ExpectedSupervisionMode
        supervisionEnabledRequired = $RequireSupervisionEnabled.IsPresent
        agentTaskRequired = $RequireAgentTask.IsPresent
    }
    app = [ordered]@{
        installed = $packageInstalled
        version = $packageVersion
        packageFullName = $packageFullName
        packageFamilyMatches = $packageFamilyMatches
        appUserModelIdRegistered = $registeredAumid
        candidatePackageFileName = if ($candidatePackagePresent) { Split-Path -Path $CandidatePackagePath -Leaf } else { $null }
        candidatePackageSha256 = $candidatePackageSha256
        candidatePackageSizeBytes = $candidatePackageSizeBytes
    }
    client = [ordered]@{
        statusPresent = $clientRead.parsed
        schemaVersion = $clientSchema
        capturedAtUtc = $clientCapturedAt
        heartbeatAgeSeconds = $clientAgeSeconds
        packageVersion = $clientPackageVersion
        statusCode = $clientStatusCode
        pendingTrialSubjectCount = $clientSubjectCount
        pendingTrialImageCount = $clientImageCount
        unexpectedFieldCount = $unexpectedClientKeyCount
        missingFieldCount = $missingClientKeyCount
    }
    agent = [ordered]@{
        installed = $agentInstalled
        executableSha256 = $agentExecutableSha256
        processCount = @($agentProcesses).Count
        matchingProcessCount = $agentMatchingProcessCount
        statusPresent = $agentStatusRead.parsed
        schemaVersion = $agentSchema
        capturedAtUtc = $agentCapturedAt
        heartbeatAgeSeconds = $agentAgeSeconds
        authCategory = $authCategory
    }
    supervision = [ordered]@{
        configured = $null -ne $supervisionSettings
        enabled = $supervisionEnabled
        mode = $supervisionMode
        state = $supervisionState
        proposedAction = $supervisionProposedAction
        healthReadState = $supervisionHealthReadState
        packageCompatibility = $packageCompatibility
        maintenanceActive = $maintenanceActive
        circuitOpenUntilUtc = $circuitOpenUntilUtc
    }
    task = $task
    rollback = [ordered]@{
        pauseCommand = "IAUWP.Agent.exe --supervision-pause indefinite"
        resumeCommand = "IAUWP.Agent.exe --supervision-resume"
        resetCircuitCommand = "IAUWP.Agent.exe --supervision-reset-circuit"
        disableSetting = "ClientSupervision.Enabled=false"
        preservesUwpLocalState = $true
    }
    checks = $checkArray
}

$json = $result | ConvertTo-Json -Depth 8
if (-not [String]::IsNullOrWhiteSpace($OutputPath)) {
    $parent = Split-Path -Path $OutputPath -Parent
    if (-not [String]::IsNullOrWhiteSpace($parent) -and -not (Test-Path -LiteralPath $parent)) {
        New-Item -ItemType Directory -Path $parent -Force | Out-Null
    }
    $json | Set-Content -LiteralPath $OutputPath -Encoding UTF8
}

$json
if ($failedCount -gt 0) {
    exit 1
}
exit 0
