Public/Invoke-BridgeStatusComparison.ps1

<#
    .SYNOPSIS
    Αντιγράφει ιδιότητες OCR από την προηγούμενη στην τρέχουσα εγγραφή γέφυρας.
 
    .DESCRIPTION
    Συμπληρώνει κενές ιδιότητες OCR στην τρέχουσα εγγραφή γέφυρας από την προηγούμενη.
 
    .PARAMETER PreviousBridge
    Η προηγούμενη εγγραφή γέφυρας.
 
    .PARAMETER CurrentBridge
    Η τρέχουσα εγγραφή γέφυρας.
 
    .PARAMETER PropertyNames
    Τα ονόματα των ιδιοτήτων προς αντιγραφή.
    #>

function Copy-BridgePreviousOcrProperty {
    [CmdletBinding()]
    param (
        [Parameter()][AllowNull()][object]$PreviousBridge,
        [Parameter()][AllowNull()][object]$CurrentBridge,
        [Parameter(Mandatory)][string[]]$PropertyNames
    )
    if (-not $PreviousBridge -or -not $CurrentBridge) {
        return
    }
    foreach ($prop in $PropertyNames) {
        if ($null -ne $PreviousBridge.$prop -and [string]::IsNullOrEmpty($CurrentBridge.$prop)) {
            $CurrentBridge | Add-Member -MemberType NoteProperty -Name $prop -Value $PreviousBridge.$prop -Force
        }
    }
}

<#
    .SYNOPSIS
    Υπολογίζει τις ακατέργαστες διαφορές μεταξύ προηγούμενης και τρέχουσας κατάστασης.
 
    .DESCRIPTION
    Επιστρέφει τα αποτελέσματα σύγκρισης για την πρώτη εκτέλεση ή μέσω Compare-Object.
 
    .PARAMETER PreviousStateArray
    Ο πίνακας προηγούμενων καταστάσεων.
 
    .PARAMETER CurrentStateArray
    Ο πίνακας τρεχουσών καταστάσεων.
 
    .PARAMETER Configuration
    Το αντικείμενο διαμόρφωσης.
    #>

function Get-BridgeRawComparisonDiff {
    [CmdletBinding()]
    [OutputType([object[]])]
    param (
        [Parameter()][AllowEmptyCollection()][object[]]$PreviousStateArray = @(),
        [Parameter(Mandatory)][object[]]$CurrentStateArray,
        [Parameter(Mandatory)][ValidateNotNull()][PSCustomObject]$Configuration
    )
    if ($PreviousStateArray.Count -eq 0) {
        return @($CurrentStateArray | ForEach-Object {
            [PSCustomObject]@{
                GefyraName    = $_.GefyraName
                GefyraStatus  = $_.GefyraStatus
                ImageUrl      = $_.ImageUrl
                ImageHash     = $_.ImageHash
                SideIndicator = '=>'
            }
        })
    }

    $scheduledStatus = $Configuration.Statuses.ClosedWithSchedule
    $hasPreviousHash = $PreviousStateArray | Where-Object { $_.GefyraStatus -eq $scheduledStatus -and $null -ne $_.ImageHash }
    $hasCurrentHash = $CurrentStateArray | Where-Object { $_.GefyraStatus -eq $scheduledStatus -and $null -ne $_.ImageHash }

    $imageProperty = if ($hasPreviousHash -and $hasCurrentHash) { 'ImageHash' } else { 'ImageUrl' }
    $compareSplat = @{
        ReferenceObject  = $PreviousStateArray
        DifferenceObject = $CurrentStateArray
        Property         = @('GefyraName', 'GefyraStatus', $imageProperty)
        IncludeEqual     = $true
    }
    return @(Compare-Object @compareSplat)
}

<#
    .SYNOPSIS
    Ελέγχει αν μια αλλαγή κατάστασης γέφυρας απαιτεί αποστολή ειδοποίησης.
 
    .DESCRIPTION
    Αξιολογεί το SideIndicator, συμπληρώνει ιδιότητες OCR όταν δεν υπάρχει ουσιαστική αλλαγή,
    και επιστρέφει $true μόνο όταν πρέπει να παραχθεί γεγονός αλλαγής.
 
    .PARAMETER Change
    Το αντικείμενο διαφοράς προς έλεγχο.
 
    .PARAMETER PreviousStateArray
    Ο πίνακας προηγούμενων καταστάσεων.
 
    .PARAMETER CurrentStateArray
    Ο πίνακας τρεχουσών καταστάσεων.
 
    .PARAMETER Configuration
    Το αντικείμενο διαμόρφωσης.
    #>

function Test-BridgeChangeIsActionable {
    [CmdletBinding()]
    [OutputType([bool])]
    param (
        [Parameter(Mandatory)][object]$Change,
        [Parameter()][AllowEmptyCollection()][object[]]$PreviousStateArray = @(),
        [Parameter(Mandatory)][object[]]$CurrentStateArray,
        [Parameter(Mandatory)][ValidateNotNull()][PSCustomObject]$Configuration
    )
    $logSplat = @{
        Level = 'Verbose'
        Stage = 'Ανάλυση'
    }
    $prevBridge = $PreviousStateArray | Where-Object { $_.GefyraName -eq $Change.GefyraName } | Select-Object -First 1
    $currentBridge = $CurrentStateArray | Where-Object { $_.GefyraName -eq $Change.GefyraName } | Select-Object -First 1

    if ($Change.SideIndicator -eq '==') {
        $logSplat.Message = "Καμία ουσιαστική αλλαγή στην $($Change.GefyraName)."
        Write-BridgeLog @logSplat -Configuration $Configuration
        $ocrProps = @('From', 'To', 'ClosedFor', 'OpensIn', 'Note1', 'Note2', 'ImageHash')
        Copy-BridgePreviousOcrProperty -PreviousBridge $prevBridge -CurrentBridge $currentBridge -PropertyNames $ocrProps
        return $false
    }

    if ($Change.SideIndicator -ne '=>') {
        return $false
    }

    if (-not $prevBridge -or $prevBridge.GefyraStatus -ne $Change.GefyraStatus) {
        return $true
    }

    if ($Change.GefyraStatus -ne $Configuration.Statuses.ClosedWithSchedule) {
        $logSplat.Message = "Καμία ουσιαστική αλλαγή στην $($Change.GefyraName)."
        Write-BridgeLog @logSplat -Configuration $Configuration
        return $false
    }

    if ($currentBridge -and [string]::IsNullOrEmpty($currentBridge.ImageHash) -and -not [string]::IsNullOrEmpty($prevBridge.ImageHash)) {
        $logSplat.Message = "Αποτυχία λήψης νέου hash. Επαναχρησιμοποίηση προηγούμενου hash για την $($Change.GefyraName)."
        Write-BridgeLog @logSplat -Configuration $Configuration
        $currentBridge.ImageHash = $prevBridge.ImageHash
        $ocrProps = @('From', 'To', 'ClosedFor', 'OpensIn', 'Note1', 'Note2')
        Copy-BridgePreviousOcrProperty -PreviousBridge $prevBridge -CurrentBridge $currentBridge -PropertyNames $ocrProps
        return $false
    }

    $logSplat.Message = "Εντοπίστηκε ενημέρωση του προγράμματος κλεισίματος για την $($Change.GefyraName)."
    Write-BridgeLog @logSplat -Configuration $Configuration
    return $true
}

<#
    .SYNOPSIS
    Συγκρίνει τις λίστες καταστάσεων γεφυρών και επιστρέφει γεγονότα διαφορών.
 
    .DESCRIPTION
    Η Invoke-BridgeStatusComparison συγκρίνει την προηγούμενη και την τρέχουσα
    κατάσταση γεφυρών, εμπλουτίζει τα δεδομένα με πληροφορίες από το προηγούμενο
    ιστορικό και παράγει αντικείμενα diff events για τις αλλαγές κατάστασης.
 
    .PARAMETER PreviousState
    Η προηγούμενη λίστα καταστάσεων.
 
    .PARAMETER CurrentState
    Η τρέχουσα λίστα καταστάσεων.
 
    .PARAMETER Configuration
    Αντικείμενο διαμόρφωσης με ρυθμίσεις, statuses και URLs.
 
    .OUTPUTS
    [PSCustomObject[]]
 
    .EXAMPLE
    Invoke-BridgeStatusComparison -PreviousState $prev -CurrentState $curr -Configuration $config
 
    .NOTES
    Λειτουργεί ως pure transformation pipeline χωρίς εξωτερικές παρενέργειες.
    #>

function Invoke-BridgeStatusComparison {
    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param (
        [Parameter()]
        [AllowNull()]
        [AllowEmptyCollection()]
        [object]$PreviousState = @(),

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [object]$CurrentState,

        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [PSCustomObject]$Configuration
    )
    try {
        $previousStateArray = @($PreviousState | Where-Object { $null -ne $_ })
        $currentStateArray = @($CurrentState | Where-Object { $null -ne $_ })

        $diffEvents = @()
        $diff = Get-BridgeRawComparisonDiff -PreviousStateArray $previousStateArray -CurrentStateArray $currentStateArray -Configuration $Configuration

        $closedStatuses = @(
            $Configuration.Statuses.ClosedForMaintenance,
            $Configuration.Statuses.ClosedWithSchedule,
            $Configuration.Statuses.PermanentlyClosed
        )

        $handlerMap = @{}
        foreach ($status in $closedStatuses) {
            $handlerMap["$status|=>"] = 'Closed'
        }
        $handlerMap["$($Configuration.Statuses.Open)|=>"] = 'Opened'

        foreach ($change in $diff) {
            $writeBridgeLogSplat = @{
                Level   = 'Verbose'
                Stage   = 'Ανάλυση'
                Message = "🌉 $($change.GefyraName) ➜ $($change.GefyraStatus) ($($change.SideIndicator))"
            }
            Write-BridgeLog @writeBridgeLogSplat -Configuration $Configuration

            $actionableSplat = @{
                Change             = $change
                PreviousStateArray = $previousStateArray
                CurrentStateArray  = $currentStateArray
                Configuration      = $Configuration
            }
            if (-not (Test-BridgeChangeIsActionable @actionableSplat)) {
                continue
            }

            $key = "$($change.GefyraStatus)|$($change.SideIndicator)"
            if ($handlerMap.ContainsKey($key)) {
                $type = $handlerMap[$key]
                $resolveBridgeStateForChangeSplat = @{
                    Change        = $change
                    PreviousState = $previousStateArray
                    CurrentState  = $currentStateArray
                }
                $changedBridgeState = Resolve-BridgeStateForChange @resolveBridgeStateForChangeSplat
                if ($changedBridgeState.Count -gt 0) {
                    $diffEvents += [PSCustomObject]@{
                        BridgeName = $change.GefyraName
                        Status     = $change.GefyraStatus
                        Type       = $type
                        StateInfo  = $changedBridgeState
                    }
                }
            } else {
                $writeBridgeLogSplat = @{
                    Stage   = 'Σφάλμα'
                    Message = "❓ Άγνωστο combo: $key"
                    Level   = 'Warning'
                }
                Write-BridgeLog @writeBridgeLogSplat -Configuration $Configuration
            }
        }
        return $diffEvents
    } catch {
        $writeBridgeLogSplat = @{
            Level   = 'Warning'
            Stage   = 'Σφάλμα'
            Message = "❌ $($_.Exception.Message)"
        }
        Write-BridgeLog @writeBridgeLogSplat -Configuration $Configuration
        throw
    }
}