```powershell
<#
.SYNOPSIS
    Validates and documents CentOS FirewallD configuration from a Windows admin workstation or jump host.

.DESCRIPTION
    This script is a safe, non-destructive validation helper for CentOS FirewallD planning and change verification.
    It checks host reachability, collects the active zone state, inspects allowed services and ports, and compares
    runtime and permanent configuration via SSH when available.

    The script does not modify firewall settings by default. It is intended to support operational review before
    and after a change window.

.NOTES
    Intended use:
    - Pre-change review of FirewallD exposure
    - Post-change validation after reload/reboot
    - Documentation of zones, services, sources, and ports

    Requirements:
    - PowerShell 5.1+ or PowerShell 7+
    - Network access to the target CentOS host
    - SSH access to the host if remote command collection is enabled
    - OpenSSH client available on the admin workstation for SSH-based checks

    Example:
    .\Get-FirewalldValidationReport.ps1 -HostName server01.example.local -SshUser admin -RunRemoteChecks
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [string]$HostName,

    [Parameter()]
    [ValidateRange(1, 65535)]
    [int]$SshPort = 22,

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [string]$SshUser,

    [Parameter()]
    [switch]$RunRemoteChecks,

    [Parameter()]
    [switch]$ExportAsMarkdown,

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [string]$OutputPath = ".\firewalld-validation-report.md"
)

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

function Test-CommandAvailable {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name
    )

    return [bool](Get-Command $Name -ErrorAction SilentlyContinue)
}

function Invoke-SshCommand {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Target,

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

        [Parameter()]
        [int]$Port = 22,

        [Parameter()]
        [string]$User
    )

    if (-not (Test-CommandAvailable -Name 'ssh')) {
        throw 'OpenSSH client (ssh) is not available on this system.'
    }

    $destination = if ($User) { "$User@$Target" } else { $Target }
    $arguments = @('-p', $Port, $destination, $Command)

    $output = & ssh @arguments 2>&1
    if ($LASTEXITCODE -ne 0) {
        throw "SSH command failed on $Target: $output"
    }

    return ($output | Out-String).Trim()
}

function Parse-KeyValueLines {
    param(
        [Parameter(Mandatory = $true)]
        [string[]]$Lines
    )

    $result = [ordered]@{}
    foreach ($line in $Lines) {
        if ($line -match '^(?<key>[^:]+):\s*(?<value>.*)$') {
            $result[$Matches.key.Trim()] = $Matches.value.Trim()
        }
    }
    return $result
}

Write-Verbose "Starting validation for $HostName"

$report = [ordered]@{
    GeneratedAt = (Get-Date).ToString('s')
    HostName    = $HostName
    SshPort     = $SshPort
    RemoteChecksRequested = [bool]$RunRemoteChecks
    Checks      = [ordered]@{}
}

# Basic connectivity test from the admin workstation
$tcpTest = $null
try {
    $tcpTest = Test-NetConnection -ComputerName $HostName -Port $SshPort -WarningAction SilentlyContinue
    $report.Checks['TcpReachability'] = [ordered]@{
        Succeeded = [bool]$tcpTest.TcpTestSucceeded
        RemoteAddress = $tcpTest.RemoteAddress
        RemotePort = $SshPort
    }
}
catch {
    $report.Checks['TcpReachability'] = [ordered]@{
        Succeeded = $false
        Error = $_.Exception.Message
    }
}

if ($RunRemoteChecks) {
    if (-not $SshUser) {
        throw 'SshUser is required when -RunRemoteChecks is specified.'
    }

    $commands = [ordered]@{
        ActiveZones = 'firewall-cmd --get-active-zones'
        AllZones = 'firewall-cmd --get-zones'
        DefaultZone = 'firewall-cmd --get-default-zone'
        RuntimeAll = 'firewall-cmd --list-all'
        PermanentAll = 'firewall-cmd --permanent --list-all'
        RuntimeServices = 'firewall-cmd --list-services'
        RuntimePorts = 'firewall-cmd --list-ports'
        ReloadState = 'firewall-cmd --state'
    }

    $remoteResults = [ordered]@{}
    foreach ($name in $commands.Keys) {
        try {
            $remoteResults[$name] = Invoke-SshCommand -Target $HostName -User $SshUser -Port $SshPort -Command $commands[$name]
        }
        catch {
            $remoteResults[$name] = "ERROR: $($_.Exception.Message)"
        }
    }

    $report.Checks['RemoteFirewallD'] = $remoteResults
}

# Build a simple Markdown report for saving or review
$markdown = New-Object System.Collections.Generic.List[string]
$markdown.Add("# FirewallD Validation Report")
$markdown.Add("")
$markdown.Add("- Generated at: $($report.GeneratedAt)")
$markdown.Add("- Host: $HostName")
$markdown.Add("- SSH port: $SshPort")
$markdown.Add("- Remote checks requested: $([bool]$RunRemoteChecks)")
$markdown.Add("")
$markdown.Add("## Connectivity")
$markdown.Add("")
if ($report.Checks.Contains('TcpReachability')) {
    $reach = $report.Checks['TcpReachability']
    $markdown.Add("- TCP reachability succeeded: $($reach.Succeeded)")
    if ($reach.Contains('RemoteAddress')) {
        $markdown.Add("- Remote address: $($reach.RemoteAddress)")
    }
    if ($reach.Contains('Error')) {
        $markdown.Add("- Error: $($reach.Error)")
    }
}
$markdown.Add("")
$markdown.Add("## Validation Notes")
$markdown.Add("")
$markdown.Add("- Confirm that the active zone matches the intended trust boundary.")
$markdown.Add("- Verify that required services are allowed only where needed.")
$markdown.Add("- Compare runtime and permanent configuration before and after reload.")
$markdown.Add("- Ensure remote administration remains available before tightening SSH exposure.")
$markdown.Add("- Review source-based exceptions for temporary access and remove them after the change window.")
$markdown.Add("")

if ($report.Checks.Contains('RemoteFirewallD')) {
    $markdown.Add("## Remote FirewallD Output")
    $markdown.Add("")
    foreach ($entry in $report.Checks['RemoteFirewallD'].GetEnumerator()) {
        $markdown.Add("### $($entry.Key)")
        $markdown.Add("")
        $markdown.Add('```text')
        $markdown.Add($entry.Value)
        $markdown.Add('```')
        $markdown.Add("")
    }
}

$finalMarkdown = $markdown -join [Environment]::NewLine

if ($ExportAsMarkdown) {
    $directory = Split-Path -Path $OutputPath -Parent
    if ($directory -and -not (Test-Path -Path $directory)) {
        New-Item -Path $directory -ItemType Directory -Force | Out-Null
    }

    $finalMarkdown | Set-Content -Path $OutputPath -Encoding UTF8
    Write-Host "Report saved to $OutputPath"
}
else {
    Write-Output $finalMarkdown
}

<#
    Optional next steps for operators:
    - Compare the runtime and permanent outputs before making any changes.
    - Validate that SSH access is preserved from the management network.
    - Re-run the script after firewall reload or reboot to confirm persistence.
#>
```