Modules/Public/Import-S2DHealthConfig.ps1

#Requires -Version 7.0
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Import-S2DHealthConfig {
    <#
    .SYNOPSIS
        Loads an external health-check configuration to override the default scoring behaviour.

    .DESCRIPTION
        Validates and activates a user-supplied health-checks.json file. The loaded config
        overrides the defaults for all subsequent Get-S2DHealthStatus calls in the current
        PowerShell session. The override is in-memory only — it does not modify the shipped
        config/health-checks.json inside the module installation.

        Use -Validate to dry-run schema validation without activating the config.
        Use -Default to reset to the shipped defaults (clear any active override).

    .PARAMETER Path
        Path to the health-checks.json file to load and activate.

    .PARAMETER Validate
        Schema-check the file without activating it. Returns a result object.

    .PARAMETER Default
        Reset to the shipped default config. Clears any active in-memory override.

    .EXAMPLE
        Import-S2DHealthConfig -Path C:\Temp\my-health-checks.json

    .EXAMPLE
        Import-S2DHealthConfig -Path C:\Temp\my-health-checks.json -Validate

    .EXAMPLE
        Import-S2DHealthConfig -Default

    .OUTPUTS
        PSCustomObject (when -Validate) | System.IO.FileInfo (when activating)
    #>

    [CmdletBinding(DefaultParameterSetName = 'Load')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Load', Position = 0)]
        [string] $Path,

        [Parameter(ParameterSetName = 'Load')]
        [switch] $Validate,

        [Parameter(Mandatory = $true, ParameterSetName = 'Default')]
        [switch] $Default
    )

    if ($Default) {
        # Clear the in-memory override and the cached default (forces reload from disk)
        $Script:S2DHealthConfig        = $null
        $Script:S2DHealthConfigDefault = $null
        Write-Verbose 'S2DHealthConfig: reset to shipped defaults.'
        return [pscustomobject]@{ reset = $true; message = 'Active config cleared — shipped defaults will be used.' }
    }

    # Resolve and validate path
    $resolvedPath = if ([System.IO.Path]::IsPathRooted($Path)) { $Path } else {
        Join-Path (Get-Location) $Path
    }
    if (-not (Test-Path -Path $resolvedPath -PathType Leaf)) {
        throw "Health config file not found: '$resolvedPath'"
    }

    # Parse JSON
    $parsed = $null
    try {
        $raw    = Get-Content -Path $resolvedPath -Raw
        $parsed = $raw | ConvertFrom-Json -Depth 20
    } catch {
        throw "Invalid JSON in '$resolvedPath': $($_.Exception.Message)"
    }

    # Schema validation: required top-level keys
    foreach ($key in @('version', 'checks')) {
        if (-not $parsed.PSObject.Properties[$key]) {
            throw "Health config '$resolvedPath' is missing required top-level key '$key'."
        }
    }

    # Each check must have id, weight, title
    $errors = New-Object System.Collections.ArrayList
    foreach ($c in @($parsed.checks)) {
        foreach ($field in @('id', 'weight', 'title')) {
            $val = if ($c.PSObject.Properties[$field]) { $c.$field } else { $null }
            if ([string]::IsNullOrWhiteSpace([string]$val)) {
                [void]$errors.Add("Check is missing '$field' (id='$($c.id)')")
            }
        }
        # Each check must have at least one threshold
        if (-not $c.thresholds -or @($c.thresholds).Count -eq 0) {
            [void]$errors.Add("Check '$($c.id)' has no thresholds defined.")
        } else {
            foreach ($t in @($c.thresholds)) {
                foreach ($tf in @('status', 'label', 'points')) {
                    $tv = if ($t.PSObject.Properties[$tf]) { $t.$tf } else { $null }
                    if ($null -eq $tv) {
                        [void]$errors.Add("Check '$($c.id)' threshold is missing '$tf'.")
                    }
                }
            }
        }
    }

    if ($errors.Count -gt 0) {
        throw "Health config schema violations in '$resolvedPath':`n - $($errors -join "`n - ")"
    }

    $checkCount = @($parsed.checks).Count

    if ($Validate) {
        Write-Verbose "Validated '$resolvedPath' ($checkCount check definitions) — not activated (dry-run)."
        return [pscustomobject]@{
            validated  = $true
            checkCount = $checkCount
            version    = [string]$parsed.version
            path       = $resolvedPath
            message    = "Validation passed ($checkCount checks). Use Import-S2DHealthConfig without -Validate to activate."
        }
    }

    # Activate: convert to hashtable and store in module-scoped variable
    $Script:S2DHealthConfig = ConvertTo-S2DHealthConfigHashtable -InputObject $parsed
    # Clear the cached default so a subsequent -Default reset works correctly
    $Script:S2DHealthConfigDefault = $null

    Write-Verbose "Imported health config from '$resolvedPath' ($checkCount checks). Active for this session."
    return [pscustomobject]@{
        activated  = $true
        checkCount = $checkCount
        version    = [string]$parsed.version
        path       = $resolvedPath
    }
}