# nr-flow-validator — Declarative Node-RED flows with conftest validation

set export

# Default: generate a flow, validate, and show results
default: validate

# ---- Flow generation ----

# Generate Node-RED flow JSON from a YAML spec
generate FLOW="zigbee-monitor":
    @echo "=== Generating {{FLOW}} ==="
    python3 scripts/flow2json.py flows/{{FLOW}}.yaml > output/{{FLOW}}.json
    @echo "Wrote output/{{FLOW}}.json"

# Generate all flows
generate-all:
    @for f in flows/*.yaml; do \
        name=$(basename "$f" .yaml); \
        just generate "$name"; \
    done

# ---- Validation ----

# Validate a generated flow against Rego policies
validate FLOW="zigbee-monitor": generate
    @echo "=== Validating {{FLOW}} ==="
    conftest test output/{{FLOW}}.json -p policy/ --namespace nodered --no-color

# Validate all flows
validate-all:
    @for f in output/*.json; do \
        echo "=== Validating $(basename "$f") ==="; \
        conftest test "$$f" -p policy/ --namespace nodered --no-color; \
    done

# ---- Deployment ----

# Deploy flow to Node-RED instance
deploy FLOW="zigbee-monitor" HOST="http://192.168.81.147:1880": generate validate
    @echo "=== Deploying {{FLOW}} to {{HOST}} ==="
    curl -s -o /dev/null -w "HTTP %{http_code}" \
        -X POST {{HOST}}/flows \
        -H "Content-Type: application/json" \
        -H "Node-RED-Deployment-Type: full" \
        -d @output/{{FLOW}}.json
    @echo ""

# Quick deploy (skip validation)
deploy-quick FLOW="zigbee-monitor" HOST="http://192.168.81.147:1880": generate
    @echo "=== Quick deploy {{FLOW}} to {{HOST}} ==="
    curl -s -o /dev/null -w "HTTP %{http_code}" \
        -X POST {{HOST}}/flows \
        -H "Content-Type: application/json" \
        -H "Node-RED-Deployment-Type: full" \
        -d @output/{{FLOW}}.json
    @echo ""

# ---- Viewing ----

# Fetch and display current flows from Node-RED
pull HOST="http://192.168.81.147:1880":
    curl -s {{HOST}}/flows | python3 -m json.tool

# Open dashboard in browser
dashboard:
    xdg-open http://192.168.81.147:1880/dashboard 2>/dev/null || open http://192.168.81.147:1880/dashboard 2>/dev/null || echo "Open: http://192.168.81.147:1880/dashboard"

# ---- Development ----

# Watch for changes and re-validate
watch FLOW="zigbee-monitor":
    @while true; do \
        inotifywait -q -e modify flows/{{FLOW}}.yaml policy/*.rego; \
        clear; just validate {{FLOW}}; \
    done

# Create a new flow from template
new NAME:
    @cp flows/zigbee-monitor.yaml flows/{{NAME}}.yaml
    @echo "Created flows/{{NAME}}.yaml — edit and run 'just validate {{NAME}}'"

# ---- Diagnostics ----
# All commands target the live RPi at $RPI_HOST (default root@192.168.81.147).
# Override with: just RPI_HOST=user@host mqtt-pub topic msg

RPI_HOST := "root@192.168.81.147"
MQTT_HOST := "192.168.81.147"
MQTT_PORT := "1883"
RPI_DIR := "/root"
NR_PORT := "1880"

# Run a command on the RPi over SSH
rpi CMD *ARGS:
    ssh -o ConnectTimeout=5 {{RPI_HOST}} '{{CMD}} {{ARGS}}'

# Publish to MQTT broker on the RPi
mqtt-pub TOPIC PAYLOAD:
    uv run --with paho-mqtt python3 scripts/mqtt.py pub --topic '{{TOPIC}}' --payload '{{PAYLOAD}}'

# Subscribe to MQTT broker on the RPi for N seconds
mqtt-sub TOPIC SECONDS="5":
    uv run --with paho-mqtt python3 scripts/mqtt.py sub --topic '{{TOPIC}}' --seconds {{SECONDS}}

# Publish a button action and watch what /set topics fire (E2E test)
mqtt-button ACTION="single":
    uv run --with paho-mqtt python3 scripts/diag.py mqtt-button --action {{ACTION}}

# Show live Node-RED flow summary (node count, function names, button wires, mqtt-out topics)
nr-show:
    python3 scripts/diag.py show

# Diff deployed Node-RED flow against repo (excluding random uuid ids)
nr-diff FLOW="zigbee-monitor": generate
    python3 scripts/diag.py diff output/{{FLOW}}.json

# Tail Node-RED container logs
nr-logs:
    ssh {{RPI_HOST}} 'docker logs --tail 50 -f nodered'

# Tail zigbee2mqtt container logs
z2m-logs:
    ssh {{RPI_HOST}} 'docker logs --tail 50 -f zigbee2mqtt'

# Tail mosquitto container logs
mqtt-logs:
    ssh {{RPI_HOST}} 'docker logs --tail 50 -f mosquitto'

# Restart a service container
restart SERVICE:
    ssh {{RPI_HOST}} 'docker restart {{SERVICE}}'

# Restart the full stack
restart-all:
    ssh {{RPI_HOST}} 'docker restart mosquitto zigbee2mqtt nodered doorbell-listener'

# Tail doorbell-listener container logs
doorbell-logs:
    ssh {{RPI_HOST}} 'docker logs --tail 50 -f doorbell-listener'

# Restart just the doorbell-listener
restart-doorbell:
    ssh {{RPI_HOST}} 'docker restart doorbell-listener'

# Publish a test alert to ntfy (used for E2E testing the listener).
# Usage: just doorbell-publish "Title" "Body"
doorbell-publish TITLE BODY="Someone is at the door":
    curl -sS -X POST "https://ntfy.sh/ALERT_klubhaus_topic_test" \
        -H "Title: {{TITLE}}" \
        -H "Priority: high" \
        -d "{{BODY}}" \
        -w "\nHTTP %{http_code}\n"

# Build + (re)start the listener (after edits to infra/doorbell-listener/)
doorbell-rebuild:
    ssh {{RPI_HOST}} 'cd {{RPI_DIR}}/nr-flow-validator && docker compose build doorbell-listener && docker compose up -d doorbell-listener'

# Patch zigbee2mqtt's bridge.js for BLZ adapter compatibility.
# The Pine64 latest-dev image crashes on start because Bridge.start() calls
# firstCoordinatorEndpoint().deviceIeeeAddress BEFORE registering the
# onMQTTMessage listener, so permit_join (and every other bridge MQTT
# request) silently no-ops.
#
# Run this after every fresh image pull OR after the zigbee2mqtt container
# is recreated (the patch is lost on image rebuild).
z2m-patch-bridge:
    @echo "Copying patch script into zigbee2mqtt container..."
    ssh {{RPI_HOST}} 'docker cp {{justfile_directory()}}/infra/zigbee2mqtt/scripts/blz-bridge-patch.js zigbee2mqtt:/tmp/blz-bridge-patch.js'
    @echo "Applying patch (idempotent)..."
    ssh {{RPI_HOST}} 'docker exec zigbee2mqtt node /tmp/blz-bridge-patch.js'
    @echo "Restarting zigbee2mqtt..."
    ssh {{RPI_HOST}} 'docker restart zigbee2mqtt'
    @echo "Done. Verify with: just z2m-logs"

# Show paired zigbee devices and their friendly names
z2m-devices:
    ssh {{RPI_HOST}} 'cat {{RPI_DIR}}/nr-flow-validator/infra/zigbee2mqtt/configuration.yaml' > /tmp/z2m.yaml
    python3 scripts/diag.py z2m-devices /tmp/z2m.yaml

# Show per-device persisted state from z2m
z2m-state:
    ssh {{RPI_HOST}} 'cat {{RPI_DIR}}/zigbee2mqtt/state.json' | python3 -m json.tool | head -60

# Open the Node-RED editor
nr-editor:
    xdg-open http://{{MQTT_HOST}}:{{NR_PORT}} 2>/dev/null || open http://{{MQTT_HOST}}:{{NR_PORT}} 2>/dev/null || echo "Open: http://{{MQTT_HOST}}:{{NR_PORT}}"

# Open the Dashboard 2.0 SPA
nr-dashboard:
    xdg-open http://{{MQTT_HOST}}:{{NR_PORT}}/dashboard 2>/dev/null || open http://{{MQTT_HOST}}:{{NR_PORT}}/dashboard 2>/dev/null || echo "Open: http://{{MQTT_HOST}}:{{NR_PORT}}/dashboard"

# Watch live MQTT traffic (matches the z2m base_topic by default)
mqtt-watch TOPIC="zigbee2mqtt/#":
    uv run --with paho-mqtt python3 scripts/mqtt.py watch --topic '{{TOPIC}}'
