modules/ISC.IQServiceSourceSetup.psm1

#Requires -Version 5.1
Set-StrictMode -Version Latest

function Import-SourceSetupModule {
    param(
        [Parameter(Mandatory)][string]$ModuleRoot,
        [Parameter(Mandatory)][string]$Name,
        [Parameter(Mandatory)][string]$FileName
    )

    if (-not (Get-Module -Name $Name)) {
        Import-Module (Join-Path $ModuleRoot $FileName) -Force -Global -WarningAction SilentlyContinue
    }
}

function Import-AgentAdapterModule {
    if (-not (Get-Module -Name 'ISC.AgentAdapter')) {
        Import-Module (Join-Path $PSScriptRoot 'ISC.AgentAdapter.psm1') -Force -WarningAction SilentlyContinue
    }
}

function Initialize-IQServiceSourceSetup {
    param(
        [string]$ModuleRoot,
        [switch]$NonInteractive
    )

    if (-not $ModuleRoot) { $ModuleRoot = $PSScriptRoot }
    Import-SourceSetupModule -ModuleRoot $ModuleRoot -Name 'ISC.OperatorConsole' -FileName 'ISC.OperatorConsole.psm1'
    Import-SourceSetupModule -ModuleRoot $ModuleRoot -Name 'ISC.OperatorToolchain' -FileName 'ISC.OperatorToolchain.psm1'
    Import-SourceSetupModule -ModuleRoot $ModuleRoot -Name 'ISC.IQService' -FileName 'ISC.IQService.psm1'
    Import-SourceSetupModule -ModuleRoot $ModuleRoot -Name 'ISC.AdLdaps' -FileName 'ISC.AdLdaps.psm1'
    Initialize-IQServiceData -NonInteractive:$NonInteractive
    Initialize-OperatorConsole -NonInteractive:$NonInteractive
}

function Get-IQServiceAgentCatalog {
    Initialize-IQServiceSourceSetup
    return [ordered]@{
        actions        = @('Status', 'Download', 'Install', 'Update', 'Uninstall', 'Start', 'Stop', 'Restart', 'SetLogLevel', 'Unblock', 'EnableLdaps')
        unsupported    = @('StreamLogs')
        requiredConfig = @('action')
        secretFields   = @('downloadUri')
        manualSteps    = @('tlsConfiguration', 'serviceLogOnAccount', 'iscSourcePanel', 'vaPemImport')
    }
}

function Get-IQServiceResolvedConfig {
    param([Parameter(Mandatory)]$Request)

    Import-AgentAdapterModule
    $config = Get-AgentRequestValue -Object $Request -Name 'config' -Default @{}
    $decisions = Get-AgentRequestValue -Object $Request -Name 'decisions' -Default @{}
    $secretRefs = Get-AgentRequestValue -Object $Request -Name 'secretRefs' -Default @{}

    $instanceName = $(if (Get-AgentRequestValue -Object $config -Name 'instanceName') { [string](Get-AgentRequestValue -Object $config -Name 'instanceName') } else { $null })
    $installPath = $(if (Get-AgentRequestValue -Object $config -Name 'installPath') { [string](Get-AgentRequestValue -Object $config -Name 'installPath') } else { $null })
    if (-not $installPath -and -not $instanceName) {
        $installPath = Get-IQServiceDefaultInstallPath
    }

    return [PSCustomObject]@{
        Action            = [string](Get-AgentRequestValue -Object $config -Name 'action')
        InstallPath       = $installPath
        InstanceName      = $instanceName
        DownloadUriRef    = $(if (Get-AgentRequestValue -Object $secretRefs -Name 'downloadUri') { [string](Get-AgentRequestValue -Object $secretRefs -Name 'downloadUri') } else { $null })
        ZipPath           = $(if (Get-AgentRequestValue -Object $config -Name 'zipPath') { [string](Get-AgentRequestValue -Object $config -Name 'zipPath') } else { $null })
        Port              = Get-AgentRequestValue -Object $config -Name 'port'
        TlsPort           = Get-AgentRequestValue -Object $config -Name 'tlsPort'
        SkipSecondary     = [bool](Get-AgentRequestValue -Object $config -Name 'skipSecondary' -Default $false)
        LogLevel          = $(if (Get-AgentRequestValue -Object $config -Name 'logLevel') { [string](Get-AgentRequestValue -Object $config -Name 'logLevel') } else { $null })
        TraceFile         = $(if (Get-AgentRequestValue -Object $config -Name 'traceFile') { [string](Get-AgentRequestValue -Object $config -Name 'traceFile') } else { $null })
        StartAfterInstall = [bool](Get-AgentRequestValue -Object $config -Name 'startAfterInstall' -Default $false)
        PemOutputPath     = $(if (Get-AgentRequestValue -Object $config -Name 'pemOutputPath') { [string](Get-AgentRequestValue -Object $config -Name 'pemOutputPath') } else { $null })
        Thumbprint        = $(if (Get-AgentRequestValue -Object $config -Name 'thumbprint') { [string](Get-AgentRequestValue -Object $config -Name 'thumbprint') } else { $null })
        DnsName           = $(if (Get-AgentRequestValue -Object $config -Name 'dnsName') { @(Get-AgentRequestValue -Object $config -Name 'dnsName') } else { @() })
        RestartNtds       = [bool](Get-AgentRequestValue -Object $config -Name 'restartNtds' -Default $false)
        CreateSelfSigned  = [bool](Get-AgentRequestValue -Object $config -Name 'createSelfSigned' -Default $false)
        ApproveDestructive = [bool](Get-AgentRequestValue -Object $decisions -Name 'approveDestructive' -Default $false)
    }
}

function New-IQServiceAgentPlan {
    param([Parameter(Mandatory)]$Request)

    Initialize-IQServiceSourceSetup
    Assert-WindowsHost
    $resolved = Get-IQServiceResolvedConfig -Request $Request
    $needsInput = [System.Collections.Generic.List[object]]::new()
    $warnings = [System.Collections.Generic.List[string]]::new()

    if ([string]::IsNullOrWhiteSpace($resolved.Action)) {
        $needsInput.Add([ordered]@{ field = 'action'; reason = 'IQService action is required.' })
    }
    if ($resolved.Action -eq 'StreamLogs') {
        $needsInput.Add([ordered]@{ field = 'action'; reason = 'StreamLogs is not supported by the agent adapter.' })
    }
    if ($resolved.Action -in @('Download', 'Install', 'Update') -and -not $resolved.DownloadUriRef -and -not $resolved.ZipPath) {
        $needsInput.Add([ordered]@{ field = 'secretRefs.downloadUri or zipPath'; reason = 'Download/Install/Update requires a signed URL reference or local zip path.' })
    }
    if ($resolved.Action -eq 'SetLogLevel' -and -not $resolved.LogLevel) {
        $needsInput.Add([ordered]@{ field = 'logLevel'; reason = 'SetLogLevel requires logLevel.' })
    }
    if ($resolved.Action -in @('Uninstall', 'Update') -and -not $resolved.ApproveDestructive) {
        $needsInput.Add([ordered]@{ field = 'decisions.approveDestructive'; reason = 'Destructive IQService action requires explicit approval.' })
    }

    $installPath = Resolve-IQServiceInstallPath -PreferredPath $resolved.InstallPath -InstanceName $resolved.InstanceName
    $snapshot = Get-IQServiceConfigurationSnapshot -InstallPath $installPath

    return [ordered]@{
        status      = if ($needsInput.Count -gt 0) { 'needsInput' } else { 'ready' }
        requestHash = (Get-AgentRequestHash -Request $Request)
        resolved    = $resolved
        discoveries = [ordered]@{ installPath = $installPath; snapshot = $snapshot }
        mutations   = @($resolved.Action)
        prerequisites = @('Windows host', 'Administrator elevation for service mutations')
        warnings    = @($warnings)
        manualSteps = @('Configure TLS certificates and service Log On account in services.msc.', 'Register IQService in ISC source panel.')
        needsInput  = @($needsInput)
    }
}

function Build-IQServiceAgentResult {
    param(
        [Parameter(Mandatory)][string]$InstallPath,
        [Parameter(Mandatory)][string]$CompletedAction,
        [object]$Extra = $null
    )

    $result = Build-IQServiceResult -InstallPath $InstallPath -CompletedAction $CompletedAction
    if ($Extra) {
        $result.verification = $Extra
    }
    return $result
}

function Invoke-IQServiceAgentApply {
    param(
        [Parameter(Mandatory)]$Request,
        [Parameter(Mandatory)]$Plan,
        [switch]$WhatIf
    )

    Initialize-IQServiceSourceSetup
    Assert-WindowsHost
    $resolved = Get-IQServiceResolvedConfig -Request $Request
    if ($Plan.resolved) {
        foreach ($prop in $Plan.resolved.PSObject.Properties) {
            $resolved.$($prop.Name) = $prop.Value
        }
    }

    $installPath = Resolve-IQServiceInstallPath -PreferredPath $resolved.InstallPath -InstanceName $resolved.InstanceName
    if ($WhatIf) {
        return [ordered]@{ status = 'whatIf'; action = $resolved.Action; installPath = $installPath }
    }

    $downloadUri = $null
    if ($resolved.DownloadUriRef) {
        $downloadUri = Resolve-AgentSecretReference -Reference $resolved.DownloadUriRef
    }

    switch ($resolved.Action) {
        'Status' {
            return Build-IQServiceAgentResult -InstallPath $installPath -CompletedAction 'Status'
        }
        'Download' {
            Save-IQServiceZip -InstallPath $installPath -DownloadUri $downloadUri -ZipPath $resolved.ZipPath
            return Build-IQServiceAgentResult -InstallPath $installPath -CompletedAction 'Download'
        }
        'Install' {
            if ($downloadUri -or $resolved.ZipPath) {
                Save-IQServiceZip -InstallPath $installPath -DownloadUri $downloadUri -ZipPath $resolved.ZipPath | Out-Null
            }
            Install-IQServiceInstance -InstallPath $installPath -Port:$resolved.Port -TlsPort:$resolved.TlsPort `
                -SkipSecondary:$resolved.SkipSecondary -StartAfterInstall:$resolved.StartAfterInstall
            return Build-IQServiceAgentResult -InstallPath $installPath -CompletedAction 'Install'
        }
        'Update' {
            Update-IQServiceInstance -InstallPath $installPath -DownloadUri $downloadUri -ZipPath $resolved.ZipPath `
                -Port:$resolved.Port -TlsPort:$resolved.TlsPort -SkipSecondary:$resolved.SkipSecondary `
                -StartAfterInstall:$resolved.StartAfterInstall
            return Build-IQServiceAgentResult -InstallPath $installPath -CompletedAction 'Update' `
                -Extra ([ordered]@{ serviceStoppedAfterUpdate = -not $resolved.StartAfterInstall })
        }
        'Uninstall' {
            Invoke-IQServiceServiceAction -ServiceAction Uninstall -InstallPath $installPath
            return Build-IQServiceAgentResult -InstallPath $installPath -CompletedAction 'Uninstall'
        }
        'Start' {
            Invoke-IQServiceServiceAction -ServiceAction Start -InstallPath $installPath
            return Build-IQServiceAgentResult -InstallPath $installPath -CompletedAction 'Start'
        }
        'Stop' {
            Invoke-IQServiceServiceAction -ServiceAction Stop -InstallPath $installPath
            return Build-IQServiceAgentResult -InstallPath $installPath -CompletedAction 'Stop'
        }
        'Restart' {
            Invoke-IQServiceServiceAction -ServiceAction Restart -InstallPath $installPath
            return Build-IQServiceAgentResult -InstallPath $installPath -CompletedAction 'Restart'
        }
        'SetLogLevel' {
            $logResult = Set-IQServiceTraceLevel -InstallPath $installPath -Level $resolved.LogLevel `
                -TraceFile $resolved.TraceFile -RestartIfRunning
            return Build-IQServiceAgentResult -InstallPath $installPath -CompletedAction 'SetLogLevel' `
                -Extra ([ordered]@{ restartPending = [bool]$logResult.RestartPending })
        }
        'Unblock' {
            $unblock = Unblock-IQServiceFiles -InstallPath $installPath
            return Build-IQServiceAgentResult -InstallPath $installPath -CompletedAction 'Unblock' `
                -Extra ([ordered]@{ unblocked = $unblock.Unblocked; checked = $unblock.Checked })
        }
        'EnableLdaps' {
            $ldaps = Enable-AdLdaps -InstallPath $installPath -PemOutputPath $resolved.PemOutputPath `
                -Thumbprint $resolved.Thumbprint -DnsName $resolved.DnsName `
                -RestartNtds:$resolved.RestartNtds -CreateSelfSigned:$resolved.CreateSelfSigned -NonInteractive
            return [ordered]@{
                status          = 'ok'
                action          = 'EnableLdaps'
                thumbprint      = $ldaps.Thumbprint
                pemDirectory    = $ldaps.Export.OutputPath
                pemChainPath    = $ldaps.Export.ChainPath
                createdCert     = $ldaps.CreatedCertificate
                ntdsRestarted   = $ldaps.NtdsRestarted
                portListening   = [bool]$ldaps.PortCheck.Listening
                manualSteps     = @(Get-AdLdapsVaManualSteps -ChainPath $ldaps.Export.ChainPath -PemDirectory $ldaps.Export.OutputPath)
            }
        }
        default {
            throw "Unsupported IQService action '$($resolved.Action)'."
        }
    }
}

Export-ModuleMember -Function @(
    'Initialize-IQServiceSourceSetup'
    'Get-IQServiceAgentCatalog'
    'Get-IQServiceResolvedConfig'
    'New-IQServiceAgentPlan'
    'Build-IQServiceAgentResult'
    'Invoke-IQServiceAgentApply'
)