#!/usr/bin/env bash
# B-enforce/run.sh - exercise the manifest engine (GE-Enforce + Install-FromManifest)
# end-to-end on the win11 VM as a synthetic shopfloor PC.
#
# Phases:
#   1. stage:     copy enforcer + lib + matrix into the VM, mount the v2 share
#   2. baseline:  run dispatcher once, verify expected state
#   3. tamper:    apply driftScenarios from matrix.json
#   4. heal:      re-run dispatcher, verify drift went away
#   5. idempotent:re-run dispatcher, verify still clean (every entry skipped)
#
# Usage:
#   ./run.sh                       # defaults to Standard / Machine
#   ./run.sh CMM ""                # CMM (no subtype)
#   ./run.sh Standard Timeclock    # explicit
#
# Environment:
#   VM_DOMAIN   override VM name (default win11)
#   SKIP_REVERT  set to skip blank-slate snapshot revert (faster iteration)

set -euo pipefail

PCTYPE="${1:-gea-shopfloor-collections}"
# Empty string (no subtype) is the new default. The legacy two-arg form
# ("Standard Machine") still works thanks to alias maps. To explicitly
# pass an empty subtype use a single arg: ./run.sh gea-shopfloor-cmm
PCSUBTYPE="${2-}"

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEST_ROOT="$(cd "$HERE/.." && pwd)"
REPO_ROOT="$(cd "$TEST_ROOT/../../../.." && pwd)"

source "$TEST_ROOT/lib/qga.sh"

# ANSI color (auto-disable if not on a tty)
if [[ -t 1 ]]; then
    G='\033[32m'; R='\033[31m'; Y='\033[33m'; D='\033[0m'
else
    G=''; R=''; Y=''; D=''
fi

phase() { printf "\n${Y}=== %s ===${D}\n" "$*"; }
ok()    { printf "${G}[ OK ]${D} %s\n" "$*"; }
fail()  { printf "${R}[FAIL]${D} %s\n" "$*"; FAILED=1; }

FAILED=0

phase "B-enforce.run for PCType=$PCTYPE PCSubType=$PCSUBTYPE"

if [[ "${SKIP_REVERT:-}" != "1" ]]; then
    vm_revert_to_blank_slate
else
    log "skipping snapshot revert (SKIP_REVERT=1)"
    vm_start_if_needed
fi

phase "1. stage enforcer + lib + matrix into VM"

# Stage the enforcer + lib in a host samba-accessible dir so the VM can pull
# them across via Z:. /home/camp/pxe-images/ is mapped to the share.
ENFORCER_STAGE="/home/camp/pxe-images/test-stage"
mkdir -p "$ENFORCER_STAGE/lib" "$ENFORCER_STAGE/test/B-enforce"
cp "$REPO_ROOT/playbook/shopfloor-setup/common/GE-Enforce.ps1"          "$ENFORCER_STAGE/"
cp "$REPO_ROOT/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1" "$ENFORCER_STAGE/lib/"
cp "$TEST_ROOT/matrix.json"                                              "$ENFORCER_STAGE/test/"
cp "$TEST_ROOT/lib/verify-state.ps1"                                     "$ENFORCER_STAGE/test/"
cp "$HERE/tamper.ps1"                                                    "$ENFORCER_STAGE/test/B-enforce/"

vm_mount_share

# Inside VM: copy from share to local C:\Tools\, prep enrollment stubs +
# fake SFLD creds in HKLM, point shopfloorShareRoot at the v2 mirror.
qga_run_ps <<EOF
\$ErrorActionPreference='Continue'

# Clean prior state
Get-Process powershell -ErrorAction SilentlyContinue | Where-Object { \$_.Id -ne \$PID } | Stop-Process -Force -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force 'C:\\Program Files\\GE\\Shopfloor' -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force 'C:\\Logs\\Shopfloor' -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force 'C:\\Tools\\test-harness' -ErrorAction SilentlyContinue

# Stage enforcer
New-Item -ItemType Directory -Force -Path 'C:\\Program Files\\GE\\Shopfloor\\lib' | Out-Null
[System.IO.File]::WriteAllBytes('C:\\Program Files\\GE\\Shopfloor\\GE-Enforce.ps1',           [System.IO.File]::ReadAllBytes('Z:\\test-stage\\GE-Enforce.ps1'))
[System.IO.File]::WriteAllBytes('C:\\Program Files\\GE\\Shopfloor\\lib\\Install-FromManifest.ps1', [System.IO.File]::ReadAllBytes('Z:\\test-stage\\lib\\Install-FromManifest.ps1'))

# Stage harness assets (matrix + verify + tamper)
New-Item -ItemType Directory -Force -Path 'C:\\Tools\\test-harness\\B-enforce' | Out-Null
[System.IO.File]::WriteAllBytes('C:\\Tools\\test-harness\\matrix.json',       [System.IO.File]::ReadAllBytes('Z:\\test-stage\\test\\matrix.json'))
[System.IO.File]::WriteAllBytes('C:\\Tools\\test-harness\\verify-state.ps1',  [System.IO.File]::ReadAllBytes('Z:\\test-stage\\test\\verify-state.ps1'))
[System.IO.File]::WriteAllBytes('C:\\Tools\\test-harness\\B-enforce\\tamper.ps1', [System.IO.File]::ReadAllBytes('Z:\\test-stage\\test\\B-enforce\\tamper.ps1'))

# Enrollment stubs
New-Item -ItemType Directory -Force -Path 'C:\\Enrollment' | Out-Null
'$PCTYPE'    | Set-Content -Path 'C:\\Enrollment\\pc-type.txt'    -Encoding ascii
'$PCSUBTYPE' | Set-Content -Path 'C:\\Enrollment\\pc-subtype.txt' -Encoding ascii
@{ shopfloorShareRoot = '\\\\$HOST_SAMBA_HOST\\pxe-images\\tsgwp00525-v2\\shared\\dt\\shopfloor'; siteName = 'WJ (test)' } | ConvertTo-Json | Set-Content -Path 'C:\\Enrollment\\site-config.json' -Encoding ascii

# SFLD cred for the dispatcher's Get-SFLDCredential lookup
\$credKey = 'HKLM:\\SOFTWARE\\GE\\SFLD\\Credentials\\samba'
New-Item -Path \$credKey -Force | Out-Null
New-ItemProperty -Path \$credKey -Name TargetHost -Value '$HOST_SAMBA_HOST' -PropertyType String -Force | Out-Null
New-ItemProperty -Path \$credKey -Name Username   -Value '$HOST_SAMBA_USER' -PropertyType String -Force | Out-Null
New-ItemProperty -Path \$credKey -Name Password   -Value '$HOST_SAMBA_PASS' -PropertyType String -Force | Out-Null

"staged"
EOF

run_dispatcher() {
    local label="$1"
    log "running GE-Enforce ($label)..."
    qga_run_ps <<'EOF' 2>&1 | tail -30
& 'C:\Program Files\GE\Shopfloor\GE-Enforce.ps1' 2>&1 | Out-Null
"--- last 25 enforce log lines ---"
$f = Get-ChildItem 'C:\Logs\Shopfloor\enforce-*.log' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($f) { Get-Content $f.FullName -Tail 25 } else { "no enforce log" }
EOF
}

# Verify wrapper: capture output, parse summary line. PS verify returns
# rc=1 when any check fails - capture that without aborting the harness.
do_verify() {
    local phase_label="$1" expect_pass="$2"
    log "verify ($phase_label, expect_pass=$expect_pass)"
    local out rc=0
    out=$(qga_run_ps <<EOF
& 'C:\\Tools\\test-harness\\verify-state.ps1' -MatrixPath 'C:\\Tools\\test-harness\\matrix.json' -PCType '$PCTYPE' -PCSubType '$PCSUBTYPE'
EOF
) || rc=$?
    echo "$out"
    local summary
    summary=$(echo "$out" | grep -oE 'verify summary: [0-9]+/[0-9]+ passed' | head -1)
    [[ -z "$summary" ]] && summary='(no summary line)'
    if echo "$out" | grep -qE '\[FAIL\]'; then
        if [[ "$expect_pass" == "true" ]]; then
            fail "$phase_label: $summary - failures present, expected all pass"
        else
            ok "$phase_label: $summary - failures present (expected)"
        fi
    else
        if [[ "$expect_pass" == "true" ]]; then
            ok "$phase_label: $summary"
        else
            fail "$phase_label: $summary - all pass, expected failures (drift not applied?)"
        fi
    fi
}

phase "2. baseline (clean dispatcher run)"
run_dispatcher "baseline"
do_verify "baseline" "true"

phase "3. tamper drift"
qga_run_ps <<EOF
& 'C:\\Tools\\test-harness\\B-enforce\\tamper.ps1' -MatrixPath 'C:\\Tools\\test-harness\\matrix.json' -PCType '$PCTYPE' -PCSubType '$PCSUBTYPE'
EOF

phase "4. heal (dispatcher re-run after drift)"
run_dispatcher "heal"
do_verify "heal" "true"

phase "5. idempotent (no-op cycle, all should skip)"
run_dispatcher "idempotent"
do_verify "idempotent" "true"

phase "result"
if [[ "$FAILED" -eq 0 ]]; then
    ok "B-enforce: all phases passed for $PCTYPE/$PCSUBTYPE"
    exit 0
else
    fail "B-enforce: at least one phase failed for $PCTYPE/$PCSUBTYPE"
    exit 1
fi
