#Requires -Version 7.0
<#
.SYNOPSIS
  End-to-end test for Phase 7 virtual tags + scripted alarms, driven via the
  Modbus CLI.

.DESCRIPTION
  Assumes the OtOpcUa server's config DB has this Phase 7 scaffolding:

    1. A Modbus DriverInstance bound to -ModbusHost, with a Tag at HR[100]
       as UInt16 published under -InputNodeId.
    2. A VirtualTag `VT_DoubledHR100` = `double(input)` where input is
       HR[100], published under -VirtualNodeId.
    3. A ScriptedAlarm `Alarm_HighHR100` that fires when VT_DoubledHR100 > 100,
       published so the client can subscribe to AlarmConditionType events.

  Three assertions:
    1. Virtual-tag bridge — modbus-cli writes HR[100]=21, OPC UA client reads
       VirtualNodeId + expects 42.
    2. Alarm fire — modbus-cli writes HR[100]=60 (VT=120, above threshold),
       OPC UA client alarms subscribe sees the condition go Active.
    3. Alarm clear — modbus-cli writes HR[100]=10 (VT=20, below threshold),
       OPC UA client sees the condition go back to Inactive.

  See scripts/smoke/seed-phase-7-smoke.sql for the seed shape. This script
  doesn't seed; it verifies the running state.

.PARAMETER ModbusHost
  Modbus simulator endpoint. Default 127.0.0.1:5502.

.PARAMETER OpcUaUrl
  OtOpcUa server endpoint.

.PARAMETER InputNodeId
  NodeId at which the server publishes HR[100] (the input tag).

.PARAMETER VirtualNodeId
  NodeId at which the server publishes VT_DoubledHR100.

.PARAMETER AlarmNodeId
  NodeId of the AlarmConditionType (or its source) the server publishes for
  Alarm_HighHR100. Alarms subscribe filters by SourceNode = this NodeId.
#>

param(
    [string]$ModbusHost = "127.0.0.1:5502",
    [string]$OpcUaUrl = "opc.tcp://localhost:4840",
    [Parameter(Mandatory)] [string]$InputNodeId,
    [Parameter(Mandatory)] [string]$VirtualNodeId,
    [string]$AlarmNodeId
)

$ErrorActionPreference = "Stop"
. "$PSScriptRoot/_common.ps1"

$hostPart, $portPart = $ModbusHost.Split(":")
$port = [int]$portPart

$modbusCli = Get-CliInvocation `
    -ProjectFolder "src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli" `
    -ExeName "otopcua-modbus-cli"
$opcUaCli = Get-CliInvocation `
    -ProjectFolder "src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI" `
    -ExeName "otopcua-cli"

$commonModbus = @("-h", $hostPart, "-p", $port)
$results = @()

# --- Assertion 1: virtual-tag bridge ------------------------------------------
Write-Header "Virtual tag — VT_DoubledHR100 = HR[100] * 2"
$inputValue = 21
$expectedVirtual = $inputValue * 2

$w = Invoke-Cli -Cli $modbusCli -Args (@("write") + $commonModbus + `
    @("-r", "HoldingRegisters", "-a", "100", "-t", "UInt16", "-v", $inputValue))
if ($w.ExitCode -ne 0) {
    Write-Fail "modbus write failed (exit=$($w.ExitCode))"
    $results += @{ Passed = $false; Reason = "seed write failed" }
}
else {
    Write-Info "wrote HR[100]=$inputValue, waiting 3s for virtual-tag engine to re-evaluate"
    Start-Sleep -Seconds 3
    $r = Invoke-Cli -Cli $opcUaCli -Args @("read", "-u", $OpcUaUrl, "-n", $VirtualNodeId)
    if ($r.ExitCode -eq 0 -and $r.Output -match "Value:\s+$expectedVirtual\b") {
        Write-Pass "virtual tag = $expectedVirtual (input * 2)"
        $results += @{ Passed = $true }
    }
    else {
        Write-Fail "expected VT = $expectedVirtual; got:"
        Write-Host $r.Output
        $results += @{ Passed = $false; Reason = "virtual tag mismatch" }
    }
}

# --- Assertion 2: scripted alarm fires ---------------------------------------
if ([string]::IsNullOrWhiteSpace($AlarmNodeId)) {
    Write-Skip "AlarmNodeId not provided — skipping alarm fire/clear assertions"
}
else {
    Write-Header "Scripted alarm — fires when VT > 100"
    $fireValue = 60   # VT = 120, above threshold
    $w = Invoke-Cli -Cli $modbusCli -Args (@("write") + $commonModbus + `
        @("-r", "HoldingRegisters", "-a", "100", "-t", "UInt16", "-v", $fireValue))
    if ($w.ExitCode -ne 0) {
        Write-Fail "modbus write failed"
        $results += @{ Passed = $false }
    }
    else {
        Write-Info "wrote HR[100]=$fireValue (VT=$($fireValue*2)); subscribing alarms for 5s"
        # otopcua-cli's `alarms` command subscribes + prints events until an
        # interrupt or timeout. We capture ~5s worth then parse for ActiveState.
        $job = Start-Job -ScriptBlock {
            param($file, $prefix, $url, $source)
            $cmdArgs = $prefix + @("alarms", "-u", $url, "-n", $source, "--duration-seconds", "5")
            & $file @cmdArgs 2>&1
        } -ArgumentList $opcUaCli.File, $opcUaCli.PrefixArgs, $OpcUaUrl, $AlarmNodeId

        $alarmOutput = Receive-Job -Job $job -Wait -AutoRemoveJob
        $alarmText = ($alarmOutput | Out-String)
        if ($alarmText -match "Active" -or $alarmText -match "HighAlarm" -or $alarmText -match "Severity") {
            Write-Pass "alarm subscription received an event"
            $results += @{ Passed = $true }
        }
        else {
            Write-Fail "expected alarm event in subscription output"
            Write-Host $alarmText
            $results += @{ Passed = $false; Reason = "alarm did not fire" }
        }
    }

    # --- Assertion 3: alarm clears ---
    Write-Header "Scripted alarm — clears when VT falls below threshold"
    $clearValue = 10  # VT = 20, below threshold
    $w = Invoke-Cli -Cli $modbusCli -Args (@("write") + $commonModbus + `
        @("-r", "HoldingRegisters", "-a", "100", "-t", "UInt16", "-v", $clearValue))
    if ($w.ExitCode -eq 0) {
        Write-Info "wrote HR[100]=$clearValue (VT=$($clearValue*2)); alarm should clear"
        # We don't re-subscribe here — the clear is asserted via the virtual
        # tag's current value (the Phase 7 engine's commitment is that state
        # propagates on the next tick; the OPC UA alarm transition follows).
        Start-Sleep -Seconds 3
        $r = Invoke-Cli -Cli $opcUaCli -Args @("read", "-u", $OpcUaUrl, "-n", $VirtualNodeId)
        if ($r.Output -match "Value:\s+$($clearValue*2)\b") {
            Write-Pass "virtual tag returned to below-threshold ($($clearValue*2))"
            $results += @{ Passed = $true }
        }
        else {
            Write-Fail "virtual tag did not reflect cleared state"
            Write-Host $r.Output
            $results += @{ Passed = $false; Reason = "clear state mismatch" }
        }
    }
}

Write-Summary -Title "Phase 7 virtual tags + scripted alarms" -Results $results
if ($results | Where-Object { -not $_.Passed }) { exit 1 }
