#!/bin/bash

# ==============================================================================
# nmcli device reapply test for bridge port VLANs
#
# This script tests that `nmcli device reapply` correctly updates VLAN
# configurations on bridge ports.
#
# Dependencies: nmcli, ip, bridge-utils (for the `bridge` command), jq
# ==============================================================================

cleanup() {
    echo "--- Cleaning up previous configurations ---"
    (
        nmcli connection delete br0+ dummy0+
        ip link del br0
        ip link del dummy0
    ) 2>/dev/null
    echo "--- Cleanup complete ---"
    echo
}

# --- Logging Helpers ---
COLOR_RED="\e[0;31m"
COLOR_GREEN="\e[0;32m"
COLOR_BLUE="\e[0;34m"
COLOR_YELLOW="\e[1;33m"
COLOR_RESET="\e[0m"

log_test() {
    printf "\n$COLOR_YELLOW========== TEST: $* ==========$COLOR_RESET\n"
}

log_step() {
    printf "$COLOR_BLUE * $* $COLOR_RESET\n"
}

# --- Check function ---
assert_vlans() {
    local expected=$1
    local device=$2

    log_step "Asserting VLAN configuration on device '$device'"

    actual=$(bridge -j vlan show dev "$device" | jq)
    expected2=$(echo "$expected" | jq)

    if [ "$actual" != "$expected2" ]; then
        echo
        echo -e "$COLOR_RED*** ERROR: VLAN configuration mismatch on device '$device' ***$COLOR_RESET"
        echo
        echo "Expected configuration:"
        echo "$expected2"
        echo
        echo "Actual configuration:"
        echo "$actual"
        echo
        echo "Diff:"
        diff -u <(echo "$expected2") <(echo "$actual")
        echo
        exit 1
    fi
    echo -e "$COLOR_GREEN--- OK: Configuration matches expected state.$COLOR_RESET"
}


# --- Main Script ---
cleanup
set -eu

# --- Setup initial bridge and dummy interface ---
log_step "Creating initial connections: br0+ and dummy0+"
nmcli connection add type bridge ifname br0 con-name br0+ \
    bridge.vlan-filtering yes bridge.vlan-default-pvid 0 autoconnect no
nmcli connection add type dummy ifname dummy0 con-name dummy0+ controller br0 autoconnect no

# --- Test Cases ---

log_test "Case 1: Reapply with no change"
log_step "Setting dummy0+ to VLAN 1 and bringing up"
nmcli connection modify dummy0+ bridge-port.vlans "1"
nmcli connection up br0+
nmcli connection up dummy0+
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":1}]}]' "dummy0"

log_step "Reapplying dummy0 configuration without changes"
nmcli device reapply dummy0
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":1}]}]' "dummy0"
nmcli connection down dummy0+
nmcli connection down br0+

log_test "Case 2: Reapply with a simple VLAN change"
log_step "Bringing up dummy0+ with VLAN 2"
nmcli connection modify dummy0+ bridge-port.vlans "2"
nmcli connection up br0+
nmcli connection up dummy0+
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":2}]}]' "dummy0"

log_step "Modifying dummy0+ to VLAN 3 and reapplying"
nmcli connection modify dummy0+ bridge-port.vlans "3"
nmcli device reapply dummy0
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":3}]}]' "dummy0"
nmcli connection down dummy0+
nmcli connection down br0+

log_test "Case 3: Reapply with a VLAN range"
log_step "Bringing up dummy0+ with VLAN range 10-12"
nmcli connection modify dummy0+ bridge-port.vlans "10-12"
nmcli connection up br0+
nmcli connection up dummy0+
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":10},{"vlan":11},{"vlan":12}]}]' "dummy0"

log_step "Modifying dummy0+ to VLAN range 20-22 and reapplying"
nmcli connection modify dummy0+ bridge-port.vlans "20-22"
nmcli device reapply dummy0
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":20},{"vlan":21},{"vlan":22}]}]' "dummy0"
nmcli connection down dummy0+
nmcli connection down br0+

log_test "Case 4: Reapply with a specific PVID set on the port"
log_step "Bringing up dummy0+ with PVID 100"
nmcli connection modify dummy0+ bridge-port.vlans "100 pvid, 101, 200"
nmcli connection up br0+
nmcli connection up dummy0+
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":100, "flags":["PVID"]},{"vlan":101},{"vlan":200}]}]' "dummy0"

log_step "Modifying dummy0+ to PVID 200 and reapplying"
nmcli connection modify dummy0+ bridge-port.vlans "100, 200 pvid"
nmcli device reapply dummy0
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":100},{"vlan":200, "flags":["PVID"]}]}]' "dummy0"
nmcli connection down dummy0+
nmcli connection down br0+

log_test "Case 5: Changing bridge default PVID and reapplying on the bridge"
log_step "Setting br0+ default PVID to 42 and clearing port-specific VLANs"
nmcli connection modify br0+ bridge.vlan-default-pvid 42
nmcli connection modify dummy0+ bridge-port.vlans ""
nmcli connection up br0+
nmcli connection up dummy0+
log_step "Port dummy0 should inherit default PVID 42 from bridge br0"
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":42, "flags":["PVID", "Egress Untagged"]}]}]' "dummy0"

log_step "Modifying bridge br0+ default PVID to 99 and reapplying to the bridge device"
nmcli connection modify br0+ bridge.vlan-default-pvid 99
nmcli device reapply br0
log_step "Port dummy0 should now have the new PVID 99"
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":99, "flags":["PVID", "Egress Untagged"]}]}]' "dummy0"
nmcli connection down dummy0+
nmcli connection down br0+

log_test "Case 6: Bridge with default PVID and port with specific VLANs"
log_step "Setting bridge default PVID to 50 and port VLANs to 60,70"
nmcli connection modify br0+ bridge.vlan-default-pvid 50
nmcli connection modify dummy0+ bridge-port.vlans "60,70"
nmcli connection up br0+
nmcli connection up dummy0+
log_step "Port dummy0 should have its own VLANs (60,70) plus the inherited PVID (50)"
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":50,"flags":["PVID", "Egress Untagged"]},{"vlan":60},{"vlan":70}]}]' "dummy0"

log_step "Reapplying dummy0 without changes"
nmcli device reapply dummy0
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":50,"flags":["PVID", "Egress Untagged"]},{"vlan":60},{"vlan":70}]}]' "dummy0"

log_step "Modifying bridge br0+ default PVID to 55 and reapplying to the bridge"
nmcli connection modify br0+ bridge.vlan-default-pvid 55
nmcli device reapply br0
log_step "Port dummy0 should now have PVID 55"
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":55,"flags":["PVID", "Egress Untagged"]},{"vlan":60},{"vlan":70}]}]' "dummy0"

log_step "Modifying port dummy0+ VLANs to 80,90 and reapplying"
nmcli connection modify dummy0+ bridge-port.vlans "80,90"
nmcli device reapply dummy0
log_step "Port dummy0 should now have VLANs 80,90 and still have PVID 55"
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":55,"flags":["PVID", "Egress Untagged"]},{"vlan":80},{"vlan":90}]}]' "dummy0"
nmcli connection down dummy0+
nmcli connection down br0+

log_test "Case 7: Explicit Untagged VLAN (Not PVID)"
log_step "Setting PVID 100 and untagged VLAN 200"
nmcli connection modify br0+ bridge.vlan-default-pvid 0
nmcli connection modify dummy0+ bridge-port.vlans "100 pvid, 200 untagged"
nmcli connection up br0+
nmcli connection up dummy0+
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":100, "flags":["PVID"]},{"vlan":200, "flags":["Egress Untagged"]}]}]' "dummy0"

log_step "Reapplying without changes"
nmcli device reapply dummy0
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":100, "flags":["PVID"]},{"vlan":200, "flags":["Egress Untagged"]}]}]' "dummy0"
nmcli connection down dummy0+
nmcli connection down br0+

log_test "Case 8: Removing a VLAN from a list"
log_step "Setting up port with VLANs 80,90"
nmcli connection modify dummy0+ bridge-port.vlans "80,90"
nmcli connection up br0+
nmcli connection up dummy0+
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":80},{"vlan":90}]}]' "dummy0"

log_step "Modifying port to only have VLAN 80 and reapplying"
nmcli connection modify dummy0+ bridge-port.vlans "80"
nmcli device reapply dummy0
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":80}]}]' "dummy0"
nmcli connection down dummy0+
nmcli connection down br0+

log_test "Case 9: Switching from Inherited to Port-Specific PVID"
log_step "Setting bridge default PVID to 42, port inherits it"
nmcli connection modify br0+ bridge.vlan-default-pvid 42
nmcli connection modify dummy0+ bridge-port.vlans ""
nmcli connection up br0+
nmcli connection up dummy0+
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":42, "flags":["PVID", "Egress Untagged"]}]}]' "dummy0"

log_step "Modifying port to have its own PVID 300 and reapplying"
nmcli connection modify dummy0+ bridge-port.vlans "300 pvid"
nmcli device reapply dummy0
log_step "Port's own PVID 300 should take precedence"
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":42, "flags":["Egress Untagged"]},{"vlan":300, "flags":["PVID"]}]}]' "dummy0"
nmcli connection down dummy0+
nmcli connection down br0+

log_test "Case 10: Switching from Port-Specific to Inherited PVID"
log_step "Setting bridge PVID to 42 and port PVID to 300"
nmcli connection modify br0+ bridge.vlan-default-pvid 42
nmcli connection modify dummy0+ bridge-port.vlans "300 pvid"
nmcli connection up br0+
nmcli connection up dummy0+
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":42, "flags":["Egress Untagged"]},{"vlan":300, "flags":["PVID"]}]}]' "dummy0"

log_step "Modifying port to remove its PVID and reapplying"
nmcli connection modify dummy0+ bridge-port.vlans "300"
nmcli device reapply dummy0
log_step "Port should fall back to inheriting bridge PVID 42"
assert_vlans '[{"ifname":"dummy0","vlans":[{"vlan":42, "flags":["PVID", "Egress Untagged"]},{"vlan":300}]}]' "dummy0"
nmcli connection down dummy0+
nmcli connection down br0+

cleanup

echo -e "$COLOR_GREEN\n*** All tests passed successfully! ***$COLOR_RESET\n"
