VBAF.Center.Scheduler.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    VBAF-Center Phase 8 — Scheduling Engine
.DESCRIPTION
    Controls how often VBAF-Center checks customer signals
    and runs the full pipeline automatically.

    Phase 14 — RED signal override raises minimum action level
    Phase 15 — Weighted average passed through full pipeline
    Phase 17 — Customer-specific thresholds honoured end-to-end

    Functions:
      Invoke-VBAFCenterRun — run full pipeline once
      Start-VBAFCenterSchedule — start automatic checking
      Get-VBAFCenterRunHistory — show recent results
#>


$script:SchedulePath = Join-Path $env:USERPROFILE "VBAFCenter\schedules"
$script:HistoryPath  = Join-Path $env:USERPROFILE "VBAFCenter\history"

function Initialize-VBAFCenterScheduleStore {
    if (-not (Test-Path $script:SchedulePath)) { New-Item -ItemType Directory -Path $script:SchedulePath -Force | Out-Null }
    if (-not (Test-Path $script:HistoryPath))  { New-Item -ItemType Directory -Path $script:HistoryPath  -Force | Out-Null }
}

# ============================================================
# INVOKE-VBAFCENTERRUN — run full pipeline once
# ============================================================
function Invoke-VBAFCenterRun {
    param(
        [Parameter(Mandatory)] [string] $CustomerID,
        [switch] $Silent
    )

    Initialize-VBAFCenterScheduleStore

    if (-not $Silent) {
        Write-Host ""
        Write-Host ("VBAF-Center Run: {0} — {1}" -f $CustomerID, (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")) -ForegroundColor Cyan
        Write-Host ""
    }

    # --------------------------------------------------------
    # Step 1 — Load schedule config
    # --------------------------------------------------------
    $schedFile = Join-Path $script:SchedulePath "$CustomerID-schedule.json"
    if (-not (Test-Path $schedFile)) {
        Write-Host "No schedule found for: $CustomerID" -ForegroundColor Red
        Write-Host "Run Start-VBAFCenterOnboarding first." -ForegroundColor Yellow
        return $null
    }
    $sched = Get-Content $schedFile -Raw | ConvertFrom-Json

    # --------------------------------------------------------
    # Step 2 — Acquire signals via Phase 3
    # Phase 14/15: returns RedSignals, YellowSignals, WeightedAvg
    # --------------------------------------------------------
    $signalResult = $null

    if (Get-Command Get-VBAFCenterAllSignals -ErrorAction SilentlyContinue) {

        $signalResult = Get-VBAFCenterAllSignals -CustomerID $CustomerID

        if ($null -eq $signalResult -or $signalResult.VBAFInput.Length -eq 0) {
            Write-Host "No signals returned — check signal configuration." -ForegroundColor Red
            return $null
        }

        $normalisedSignals = [double[]] $signalResult.VBAFInput
        $weightedAvg       = $signalResult.WeightedAvg
        $redSignals        = $signalResult.RedSignals
        $yellowSignals     = $signalResult.YellowSignals

    } else {

        # Phase 3 not loaded — use legacy inline signal reading
        Write-Host " [WARN] Get-VBAFCenterAllSignals not available — using legacy signal read." -ForegroundColor Yellow

        $sigPath   = Join-Path $env:USERPROFILE "VBAFCenter\signals"
        $sigFiles  = Get-ChildItem $sigPath -Filter "$CustomerID-*.json" -ErrorAction SilentlyContinue
        $normalisedSignals = @()

        foreach ($sf in $sigFiles) {
            $sc     = Get-Content $sf.FullName -Raw | ConvertFrom-Json
            [double] $range = $sc.RawMax - $sc.RawMin
            [double] $raw   = $sc.RawMin + (Get-Random -Minimum 0 -Maximum 100) / 100.0 * $range
            [double] $norm  = if ($range -gt 0) { ($raw - $sc.RawMin) / $range } else { 0.0 }
            $normalisedSignals += [Math]::Max(0.0, [Math]::Min(1.0, $norm))
        }

        if ($normalisedSignals.Count -eq 0) {
            $normalisedSignals = @(
                [double](Get-Random -Minimum 0 -Maximum 100) / 100.0,
                [double](Get-Random -Minimum 0 -Maximum 100) / 100.0
            )
        }

        $weightedAvg   = -1
        $redSignals    = @()
        $yellowSignals = @()
    }

    # --------------------------------------------------------
    # Step 3 — Route to agent via Phase 5
    # Phase 14: passes RedSignals and YellowSignals
    # Phase 15: passes WeightedAvg
    # Phase 17: Phase 5 reads customer thresholds from schedule.json
    # --------------------------------------------------------
    $routeResult  = $null
    [int] $action = 0
    [string] $actionReason   = ""
    [bool]   $overrideApplied = $false
    [int]    $redCount        = 0
    [int]    $yellowCount     = 0
    [double] $avgUsed         = 0.0

    if (Get-Command Invoke-VBAFCenterRoute -ErrorAction SilentlyContinue) {

        $routeResult = Invoke-VBAFCenterRoute `
            -CustomerID        $CustomerID `
            -NormalisedSignals $normalisedSignals `
            -WeightedAvg       $weightedAvg `
            -RedSignals        $redSignals `
            -YellowSignals     $yellowSignals

        if ($null -eq $routeResult) {
            Write-Host "Routing failed — check agent configuration." -ForegroundColor Red
            return $null
        }

        $action          = $routeResult.FinalAction
        $actionReason    = $routeResult.ActionReason
        $overrideApplied = $routeResult.OverrideApplied
        $redCount        = $routeResult.RedSignalCount
        $yellowCount     = $routeResult.YellowSignalCount
        $avgUsed         = $routeResult.AvgUsed

    } else {

        # Phase 5 not loaded — use legacy inline rule-based routing
        Write-Host " [WARN] Invoke-VBAFCenterRoute not available — using legacy routing." -ForegroundColor Yellow

        [double] $avg = 0.0
        foreach ($s in $normalisedSignals) { $avg += $s }
        if ($normalisedSignals.Length -gt 0) { $avg /= $normalisedSignals.Length }

        $action       = if      ($avg -lt 0.25) { 0 }
                        elseif  ($avg -lt 0.50) { 1 }
                        elseif  ($avg -lt 0.75) { 2 }
                        else                    { 3 }
        $actionReason = ("Legacy average {0:F4}" -f $avg)
        $avgUsed      = $avg
    }

    # --------------------------------------------------------
    # Step 4 — Interpret action — read customer action map
    # --------------------------------------------------------
    $actionNames   = @("Monitor","Reassign","Reroute","Escalate")
    $actionDefaults = @("No action needed","Reassign resource","Switch approach","Emergency response")
    $actionName    = $actionNames[$action]
    $actionCommand = $actionDefaults[$action]

    $actFile = Join-Path $env:USERPROFILE "VBAFCenter\actions\$CustomerID-actions.txt"
    if (Test-Path $actFile) {
        $lines = Get-Content $actFile
        foreach ($line in $lines) {
            $parts = $line -split "\|"
            if ($parts.Length -ge 3 -and [int]$parts[0] -eq $action) {
                $actionName    = $parts[1]
                $actionCommand = $parts[2]
                break
            }
        }
    }

    # --------------------------------------------------------
    # Step 5 — Log result to history
    # Now includes Phase 14/15 fields for trend analysis
    # --------------------------------------------------------
    $result = [PSCustomObject] @{
        CustomerID       = $CustomerID
        Timestamp        = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss.fff")
        Signals          = $normalisedSignals
        AvgSignal        = [Math]::Round($avgUsed, 4)
        WeightedAvg      = if ($weightedAvg -ge 0) { [Math]::Round($weightedAvg, 4) } else { $null }
        Action           = $action
        ActionName       = $actionName
        ActionCommand    = $actionCommand
        ActionReason     = $actionReason
        OverrideApplied  = $overrideApplied
        RedSignalCount   = $redCount
        YellowSignalCount = $yellowCount
    }

    $histFile = Join-Path $script:HistoryPath "$CustomerID-$(Get-Date -Format 'yyyyMMdd_HHmmss_fff').json"
    $result | ConvertTo-Json -Depth 5 | Set-Content $histFile -Encoding UTF8

    # --------------------------------------------------------
    # Step 6 — Display
    # --------------------------------------------------------
    if (-not $Silent) {
        $sigStr = ($normalisedSignals | ForEach-Object { $_.ToString("F2") }) -join ", "
        $color  = if ($action -ge 3) { "Red" } elseif ($action -ge 2) { "DarkYellow" } else { "Green" }

        Write-Host (" Signals : [{0}]"              -f $sigStr)       -ForegroundColor White
        Write-Host (" Avg used : {0:F4}"             -f $avgUsed)      -ForegroundColor White

        if ($null -ne $result.WeightedAvg) {
            Write-Host (" Weighted : {0:F4}"         -f $result.WeightedAvg) -ForegroundColor Cyan
        }

        if ($redCount -gt 0) {
            Write-Host (" Red signals : {0}"       -f $redCount)     -ForegroundColor Red
        }
        if ($yellowCount -gt 0) {
            Write-Host (" Yellow signals : {0}"       -f $yellowCount)  -ForegroundColor Yellow
        }
        if ($overrideApplied) {
            Write-Host (" OVERRIDE : {0}"            -f $actionReason) -ForegroundColor Red
        }

        Write-Host (" Action : {0} — {1}"          -f $action, $actionName)    -ForegroundColor $color
        Write-Host (" Command : {0}"                 -f $actionCommand)          -ForegroundColor $color

        if (-not $overrideApplied -and $actionReason -ne "") {
            Write-Host (" Reason : {0}"            -f $actionReason) -ForegroundColor DarkGray
        }

        Write-Host ""
    }

    # --------------------------------------------------------
    # Step 7 — Crisis response on Action 3
    # Fires on actual Action 3 OR when RED override raised to 3
    # --------------------------------------------------------
    if ($action -ge 3) {

        if (-not $Silent) {
            Write-Host ""
            Write-Host " [CRISIS] Action 3 detected — activating Crisis Response Tree!" -ForegroundColor Red
            if ($overrideApplied) {
                Write-Host " [CRISIS] Triggered by RED signal threshold override." -ForegroundColor Red
            }
            Write-Host ""
        }

        # Sound alarm — 3 escalating beeps
        try {
            [Console]::Beep(800,  400)
            Start-Sleep -Milliseconds 100
            [Console]::Beep(1000, 400)
            Start-Sleep -Milliseconds 100
            [Console]::Beep(1500, 800)
            if (-not $Silent) { Write-Host " [NOTIFY] Sound alarm fired." -ForegroundColor Yellow }
        } catch {
            if (-not $Silent) { Write-Host " [NOTIFY] Sound alarm failed." -ForegroundColor DarkGray }
        }

        # Persistent red popup — stays until dispatcher clicks OK
        try {
            Add-Type -AssemblyName System.Windows.Forms
            Add-Type -AssemblyName System.Drawing

            $overrideNote = if ($overrideApplied) { "`nCause : RED signal threshold override" } else { "" }

            $form               = New-Object System.Windows.Forms.Form
            $form.Text          = "VBAF CRISIS ALERT"
            $form.Size          = New-Object System.Drawing.Size(440, 240)
            $form.StartPosition = "CenterScreen"
            $form.TopMost       = $true
            $form.BackColor     = [System.Drawing.Color]::Red

            $label              = New-Object System.Windows.Forms.Label
            $label.Text         = ("CRISIS DETECTED!`n`nCustomer : {0}`nAction : Escalate`nCommand : {1}{2}`n`nClick OK to continue." -f `
                                    $CustomerID, $actionCommand, $overrideNote)
            $label.ForeColor    = [System.Drawing.Color]::White
            $label.Font         = New-Object System.Drawing.Font("Arial", 10, [System.Drawing.FontStyle]::Bold)
            $label.Size         = New-Object System.Drawing.Size(410, 150)
            $label.Location     = New-Object System.Drawing.Point(10, 10)

            $button             = New-Object System.Windows.Forms.Button
            $button.Text        = "OK — I am handling it"
            $button.Size        = New-Object System.Drawing.Size(200, 35)
            $button.Location    = New-Object System.Drawing.Point(110, 170)
            $button.BackColor   = [System.Drawing.Color]::White
            $button.ForeColor   = [System.Drawing.Color]::Red
            $button.Font        = New-Object System.Drawing.Font("Arial", 10, [System.Drawing.FontStyle]::Bold)
            $button.Add_Click({ $form.Close() })

            $form.Controls.Add($label)
            $form.Controls.Add($button)
            $form.Add_Shown({ $form.Activate() })
            $form.ShowDialog() | Out-Null

            if (-not $Silent) { Write-Host " [NOTIFY] Crisis popup dismissed by dispatcher." -ForegroundColor Green }
        } catch {
            if (-not $Silent) { Write-Host " [NOTIFY] Popup failed — $($_.Exception.Message)" -ForegroundColor DarkGray }
        }

        # Email alert — configure AlertEmail in customer schedule file
        if (Test-Path $schedFile) {
            $schedData = Get-Content $schedFile -Raw | ConvertFrom-Json
            if ($schedData.AlertEmail -and $schedData.AlertEmail -ne "") {
                try {
                    Send-MailMessage `
                        -To         $schedData.AlertEmail `
                        -From       "vbaf@yourdomain.dk" `
                        -Subject    ("VBAF CRISIS — Action 3 fired for {0}" -f $CustomerID) `
                        -Body       ("VBAF-Center detected a critical situation for {0} at {1}.`n`nAction : Escalate`nCommand : {2}`nReason : {3}`n`nLog in to VBAF-Center immediately." -f `
                                     $CustomerID, (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $actionCommand, $actionReason) `
                        -SmtpServer "smtp.yourdomain.dk"
                    if (-not $Silent) { Write-Host " [NOTIFY] Email sent to $($schedData.AlertEmail)." -ForegroundColor Yellow }
                } catch {
                    if (-not $Silent) { Write-Host " [NOTIFY] Email failed — check SMTP settings." -ForegroundColor DarkGray }
                }
            }
        }

        # Activate crisis tree if loaded
        if (Get-Command Start-VBAFCenterCrisis -ErrorAction SilentlyContinue) {
            Start-VBAFCenterCrisis -CustomerID $CustomerID
        } else {
            if (-not $Silent) {
                Write-Host " Load VBAF.Center.CrisisTree.ps1 to activate crisis response." -ForegroundColor Yellow
            }
        }
    }

    return $result
}

# ============================================================
# START-VBAFCENTERSCHEDULE — loop until stopped
# ============================================================
function Start-VBAFCenterSchedule {
    param(
        [Parameter(Mandatory)] [string] $CustomerID,
        [int] $MaxRuns = 0   # 0 = run forever until Ctrl+C
    )

    $schedFile = Join-Path $script:SchedulePath "$CustomerID-schedule.json"
    if (-not (Test-Path $schedFile)) {
        Write-Host "No schedule found for: $CustomerID" -ForegroundColor Red
        return
    }

    $sched = Get-Content $schedFile -Raw | ConvertFrom-Json
    [int] $intervalSec = $sched.IntervalMinutes * 60
    [int] $runCount    = 0

    Write-Host ""
    Write-Host "VBAF-Center Schedule Started" -ForegroundColor Cyan
    Write-Host (" Customer : {0}"            -f $CustomerID)              -ForegroundColor White
    Write-Host (" Interval : every {0} minutes" -f $sched.IntervalMinutes) -ForegroundColor White
    Write-Host " Press Ctrl+C to stop."                                     -ForegroundColor DarkGray
    Write-Host ""

    while ($true) {
        $runCount++
        Write-Host (" [{0}] Run #{1}" -f (Get-Date).ToString("HH:mm:ss"), $runCount) -ForegroundColor DarkGray

        Invoke-VBAFCenterRun -CustomerID $CustomerID -Silent:$false | Out-Null

        if ($MaxRuns -gt 0 -and $runCount -ge $MaxRuns) {
            Write-Host "Max runs reached. Stopping." -ForegroundColor Yellow
            break
        }

        Write-Host (" Next run in {0} minutes..." -f $sched.IntervalMinutes) -ForegroundColor DarkGray
        Start-Sleep -Seconds $intervalSec
    }
}

# ============================================================
# GET-VBAFCENTERRUNHISTORY
# ============================================================
function Get-VBAFCenterRunHistory {
    param(
        [Parameter(Mandatory)] [string] $CustomerID,
        [int] $Last = 10
    )

    Initialize-VBAFCenterScheduleStore

    $files = Get-ChildItem $script:HistoryPath -Filter "$CustomerID-*.json" |
             Sort-Object LastWriteTime -Descending |
             Select-Object -First $Last

    if ($files.Count -eq 0) {
        Write-Host "No run history for: $CustomerID" -ForegroundColor Yellow
        return
    }

    Write-Host ""
    Write-Host "Run History: $CustomerID (last $($files.Count) runs)" -ForegroundColor Cyan
    Write-Host (" {0,-23} {1,-4} {2,-12} {3,-6} {4,-6} {5}" -f `
        "Timestamp","Act","Name","Red","Yellow","Reason / Command") -ForegroundColor Yellow
    Write-Host (" {0}" -f ("-" * 90)) -ForegroundColor DarkGray

    foreach ($f in $files) {
        $r     = Get-Content $f.FullName -Raw | ConvertFrom-Json
        $color = if ($r.Action -ge 3) { "Red" } elseif ($r.Action -ge 2) { "Yellow" } else { "Green" }

        $redCol    = if ($null -ne $r.RedSignalCount    -and $r.RedSignalCount    -gt 0) { $r.RedSignalCount.ToString()    } else { "-" }
        $yellowCol = if ($null -ne $r.YellowSignalCount -and $r.YellowSignalCount -gt 0) { $r.YellowSignalCount.ToString() } else { "-" }
        $reasonCol = if ($r.OverrideApplied) { "[OVERRIDE] $($r.ActionReason)" } else { $r.ActionCommand }

        Write-Host (" {0,-23} {1,-4} {2,-12} {3,-6} {4,-6} {5}" -f `
            $r.Timestamp, $r.Action, $r.ActionName,
            $redCol, $yellowCol, $reasonCol) -ForegroundColor $color
    }
    Write-Host ""
}

# ============================================================
# LOAD MESSAGE
# ============================================================
Write-Host "VBAF-Center Phase 8 loaded [Scheduling Engine + Phase 14/15/17 pipeline]" -ForegroundColor Cyan
Write-Host " Invoke-VBAFCenterRun — run full pipeline once"    -ForegroundColor White
Write-Host " Start-VBAFCenterSchedule — start auto-checking"       -ForegroundColor White
Write-Host " Get-VBAFCenterRunHistory — show recent results"       -ForegroundColor White
Write-Host ""