#!/bin/bash
# ══════════════════════════════════════════════════════════════════
#  secubox-net-detect — Automatic Network Interface Detection
#  Detects board type and configures WAN/LAN interfaces accordingly
#  Supports: EspressoBin, MochaBin, x64 (VM/Bare-metal)
# ══════════════════════════════════════════════════════════════════
set -u  # Exit on undefined variables, but allow command failures

SECUBOX_CONF="/etc/secubox/secubox.conf"
NETPLAN_DIR="/etc/netplan"
OUTPUT_JSON="${1:-/run/secubox/net-detect.json}"

# Colors for terminal output
RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; NC='\033[0m'
log() { echo -e "${CYAN}[net-detect]${NC} $*"; }
ok()  { echo -e "${GREEN}[  OK  ]${NC} $*"; }

# ── Board Detection ────────────────────────────────────────────────
detect_board() {
    local board="unknown"

    # ARM: Read device-tree model
    if [[ -f /proc/device-tree/model ]]; then
        local model
        model=$(tr -d '\0' < /proc/device-tree/model)

        case "$model" in
            *"MOCHAbin"*|*"mochabin"*|*"7040"*)
                board="mochabin"
                ;;
            *"ESPRESSObin Ultra"*|*"espressobin-ultra"*)
                board="espressobin-ultra"
                ;;
            *"ESPRESSObin"*|*"espressobin"*|*"3720"*)
                board="espressobin-v7"
                ;;
            *"Marvell Armada"*)
                # Generic Marvell detection
                if grep -q "7040" /proc/device-tree/compatible 2>/dev/null; then
                    board="mochabin"
                elif grep -q "3720" /proc/device-tree/compatible 2>/dev/null; then
                    board="espressobin-v7"
                fi
                ;;
        esac
    # x64: Check DMI or hostname
    elif [[ -f /sys/class/dmi/id/product_name ]]; then
        local product
        product=$(cat /sys/class/dmi/id/product_name 2>/dev/null || echo "")

        case "$product" in
            *"VirtualBox"*|*"QEMU"*|*"VMware"*|*"KVM"*)
                board="x64-vm"
                ;;
            *)
                board="x64-baremetal"
                ;;
        esac
    fi

    echo "$board"
}

# ── Interface Discovery ────────────────────────────────────────────
# Returns all physical ethernet interfaces (excludes lo, veth, docker, bridge, wg)
get_physical_interfaces() {
    local ifaces=""
    for iface in /sys/class/net/*; do
        local name
        name=$(basename "$iface")

        # Skip non-physical interfaces
        [[ "$name" == "lo" ]] && continue
        [[ "$name" == veth* ]] && continue
        [[ "$name" == docker* ]] && continue
        [[ "$name" == br-* ]] && continue
        [[ "$name" == wg* ]] && continue
        [[ "$name" == tun* ]] && continue
        [[ "$name" == tap* ]] && continue

        # Check if it's a physical device (has device/driver)
        if [[ -d "$iface/device" ]] || [[ "$name" == lan* ]] || [[ "$name" == eth* ]] || [[ "$name" == enp* ]]; then
            ifaces="$ifaces $name"
        fi
    done

    echo "$ifaces" | xargs  # Trim whitespace
}

# ── Check Interface Connectivity ────────────────────────────────────
# Returns 1 if interface has link (cable connected), 0 otherwise
check_link() {
    local iface="$1"
    local carrier_file="/sys/class/net/${iface}/carrier"

    if [[ -f "$carrier_file" ]]; then
        local carrier
        carrier=$(cat "$carrier_file" 2>/dev/null || echo "0")
        [[ "$carrier" == "1" ]] && return 0
    fi

    return 1
}

# ── Check if Interface has DHCP ─────────────────────────────────────
# Brief DHCP test on interface to detect WAN
test_dhcp() {
    local iface="$1"
    local timeout="${2:-5}"

    # Bring interface up
    ip link set "$iface" up 2>/dev/null || true
    sleep 1

    # Try dhclient briefly
    if command -v dhclient &>/dev/null; then
        timeout "$timeout" dhclient -1 -v "$iface" &>/dev/null && return 0
    elif command -v udhcpc &>/dev/null; then
        timeout "$timeout" udhcpc -i "$iface" -t 3 -n &>/dev/null && return 0
    fi

    return 1
}

# ── Board-Specific Interface Mapping ─────────────────────────────────
get_interface_config() {
    local board="$1"
    local wan_iface=""
    local lan_ifaces=""
    local sfp_ifaces=""
    local profile="full"

    case "$board" in
        mochabin)
            # MochaBin: Armada 7040 with DSA switch + SFP+
            wan_iface="eth0"
            lan_ifaces="eth1 eth2 eth3 eth4"
            sfp_ifaces="eth5 eth6"
            profile="full"
            ;;
        espressobin-v7)
            # ESPRESSObin v7: Armada 3720 with DSA (lan0/lan1)
            wan_iface="eth0"
            lan_ifaces="lan0 lan1"
            profile="lite"
            ;;
        espressobin-ultra)
            # ESPRESSObin Ultra: More LAN ports
            wan_iface="eth0"
            lan_ifaces="lan0 lan1 lan2 lan3"
            profile="lite"
            ;;
        x64-vm)
            # VMs: All interfaces get DHCP (no bridge/router mode)
            # VirtualBox/QEMU host-only adapters have their own DHCP
            local all_ifaces
            all_ifaces=$(get_physical_interfaces)
            log "VM detected - all interfaces will use DHCP: $all_ifaces"

            # First interface is WAN, no LAN bridge needed
            wan_iface=$(echo "$all_ifaces" | awk '{print $1}')
            lan_ifaces=""  # Empty = no bridge created
            profile="vm"
            ;;
        x64-baremetal)
            # Baremetal x64: Auto-detect WAN/LAN based on connectivity
            local all_ifaces
            all_ifaces=$(get_physical_interfaces)
            log "Physical interfaces found: $all_ifaces"

            # Try to find WAN: interface with link that gets DHCP
            for iface in $all_ifaces; do
                if check_link "$iface"; then
                    log "Interface $iface has carrier (link up)"
                    # First interface with link is likely WAN
                    if [[ -z "$wan_iface" ]]; then
                        wan_iface="$iface"
                    else
                        lan_ifaces="$lan_ifaces $iface"
                    fi
                else
                    log "Interface $iface has no carrier"
                fi
            done

            # If no link detected, use naming convention with priority
            if [[ -z "$wan_iface" ]]; then
                log "No link detected, using naming convention fallback"
                # Sort interfaces to get predictable WAN selection
                # Priority: eth0 > enp*s0 > eno1 > ens* > others
                local sorted_ifaces
                sorted_ifaces=$(echo "$all_ifaces" | tr ' ' '\n' | sort -V | tr '\n' ' ')

                for iface in $sorted_ifaces; do
                    case "$iface" in
                        # High priority: common WAN interface names
                        eth0|enp0s3|enp1s0|enp2s0|enp3s0|eno1|eno0|ens33|ens192)
                            if [[ -z "$wan_iface" ]]; then
                                wan_iface="$iface"
                                log "Selected $iface as WAN (naming priority)"
                            else
                                lan_ifaces="$lan_ifaces $iface"
                            fi
                            ;;
                        # Medium priority: any enp*s0 (first slot on each bus)
                        enp[0-9]*s0)
                            if [[ -z "$wan_iface" ]]; then
                                wan_iface="$iface"
                                log "Selected $iface as WAN (pattern enp*s0)"
                            else
                                lan_ifaces="$lan_ifaces $iface"
                            fi
                            ;;
                        # Medium priority: any eno* (onboard)
                        eno[0-9]*)
                            if [[ -z "$wan_iface" ]]; then
                                wan_iface="$iface"
                                log "Selected $iface as WAN (pattern eno*)"
                            else
                                lan_ifaces="$lan_ifaces $iface"
                            fi
                            ;;
                        *)
                            # Everything else goes to LAN
                            lan_ifaces="$lan_ifaces $iface"
                            ;;
                    esac
                done

                # Last resort: use first interface as WAN
                if [[ -z "$wan_iface" ]] && [[ -n "$all_ifaces" ]]; then
                    wan_iface=$(echo "$all_ifaces" | awk '{print $1}')
                    lan_ifaces=$(echo "$all_ifaces" | awk '{$1=""; print $0}')
                    log "Fallback: using first interface $wan_iface as WAN"
                fi
            fi

            profile="full"
            ;;
        *)
            # Unknown: Use first interface as WAN, rest as LAN
            local all_ifaces
            all_ifaces=$(get_physical_interfaces)
            wan_iface=$(echo "$all_ifaces" | awk '{print $1}')
            lan_ifaces=$(echo "$all_ifaces" | awk '{$1=""; print $0}' | xargs)
            profile="lite"
            ;;
    esac

    # Trim whitespace
    lan_ifaces=$(echo "$lan_ifaces" | xargs)
    sfp_ifaces=$(echo "$sfp_ifaces" | xargs)

    # Output as JSON
    cat <<EOF
{
    "board": "$board",
    "profile": "$profile",
    "interfaces": {
        "wan": "$wan_iface",
        "lan": "$lan_ifaces",
        "sfp": "$sfp_ifaces"
    },
    "detected_at": "$(date -Iseconds)"
}
EOF
}

# ── Generate Netplan Configuration ──────────────────────────────────
generate_netplan() {
    local board="$1"
    local wan_iface="$2"
    local lan_ifaces="$3"
    local sfp_ifaces="${4:-}"
    local mode="${5:-router}"

    local netplan_file="${NETPLAN_DIR}/00-secubox.yaml"

    log "Generating netplan: WAN=$wan_iface LAN=[$lan_ifaces] mode=$mode board=$board"

    # Build ethernets section (4 spaces for interface name, 6 spaces for properties)
    local ethernets=""

    # VM special case: ALL interfaces get DHCP
    if [[ "$board" == "x64-vm" ]]; then
        log "VM mode: configuring all physical interfaces with DHCP"
        local all_ifaces
        all_ifaces=$(get_physical_interfaces)
        for iface in $all_ifaces; do
            ethernets+="    ${iface}:
      dhcp4: true
      dhcp6: false
      optional: true
"
        done
    else
        # Standard WAN interface - DHCP
        if [[ -n "$wan_iface" ]]; then
            ethernets+="    ${wan_iface}:
      dhcp4: true
      dhcp6: false
      dhcp4-overrides:
        use-dns: true
        use-routes: true
      optional: true
"
        fi
    fi

    # LAN interfaces - no IP, will be bridged (not for VMs)
    for iface in $lan_ifaces; do
        ethernets+="    ${iface}:
      dhcp4: false
      optional: true
"
    done

    # SFP interfaces (if any)
    for iface in $sfp_ifaces; do
        ethernets+="    ${iface}:
      dhcp4: false
      optional: true
"
    done

    # Build bridges section
    local bridges=""
    local lan_array=""
    for iface in $lan_ifaces; do
        [[ -n "$lan_array" ]] && lan_array+=", "
        lan_array+="${iface}"
    done

    case "$mode" in
        router)
            if [[ -n "$lan_array" ]]; then
                bridges="  bridges:
    br-lan:
      interfaces: [${lan_array}]
      addresses: [192.168.10.1/24]
      dhcp4: false
      optional: true
      parameters:
        stp: false
        forward-delay: 0
"
            else
                # No LAN interfaces — DON'T create an empty br-lan with
                # 192.168.10.1/24. A member-less bridge with a static IP
                # squats the .1 address without routing anything, makes
                # systemd-networkd think the interface is "configured",
                # and frequently breaks DHCP on the real WAN NIC (the
                # router refuses a second .1/24 on its broadcast domain
                # or the bridge claims the gateway slot).
                # Live USB and single-NIC installs should just stay
                # WAN-only via the ethernet block above.
                bridges=""
            fi
            ;;
        bridge)
            # Bridge mode: all interfaces in single bridge, WAN DHCP
            bridges="  bridges:
    br0:
      interfaces: [${lan_array}]
      dhcp4: true
      dhcp6: false
"
            ;;
        single)
            # Single interface mode: WAN only, no bridge
            bridges=""
            ;;
    esac

    # Write netplan config
    cat > "$netplan_file" <<EOF
# /etc/netplan/00-secubox.yaml
# Generated by secubox-net-detect
# Board: ${board} | Mode: ${mode}
network:
  version: 2
  renderer: networkd

  ethernets:
${ethernets}
${bridges}
  # DHCP server on br-lan managed by dnsmasq (secubox-nac)
EOF

    log "Netplan configuration written to: $netplan_file"
}

# ── Main ────────────────────────────────────────────────────────────
main() {
    local action="${1:-detect}"
    local mode="${2:-router}"

    log "=== SecuBox Network Detection ==="

    # Detect board
    local board
    board=$(detect_board)
    log "Board detected: $board"

    # Get interface configuration
    local config
    config=$(get_interface_config "$board")

    # Parse JSON (simple extraction)
    local wan_iface lan_ifaces sfp_ifaces profile
    wan_iface=$(echo "$config" | grep -o '"wan": "[^"]*"' | cut -d'"' -f4)
    lan_ifaces=$(echo "$config" | grep -o '"lan": "[^"]*"' | cut -d'"' -f4)
    sfp_ifaces=$(echo "$config" | grep -o '"sfp": "[^"]*"' | cut -d'"' -f4)
    profile=$(echo "$config" | grep -o '"profile": "[^"]*"' | cut -d'"' -f4)

    log "WAN interface: $wan_iface"
    log "LAN interfaces: $lan_ifaces"
    [[ -n "$sfp_ifaces" ]] && log "SFP interfaces: $sfp_ifaces"
    log "Profile: $profile"

    # VMs use single mode (no bridge) - override any default
    if [[ "$profile" == "vm" ]]; then
        mode="single"
        log "VM profile detected - using single mode (no bridge)"
    fi

    case "$action" in
        detect)
            # Output detection result
            mkdir -p "$(dirname "$OUTPUT_JSON")"
            echo "$config" > "$OUTPUT_JSON"
            ok "Detection result: $OUTPUT_JSON"
            echo "$config"
            ;;
        apply)
            # Generate and apply netplan configuration
            generate_netplan "$board" "$wan_iface" "$lan_ifaces" "$sfp_ifaces" "$mode"
            log "Applying netplan..."
            # Use timeout to prevent hanging
            timeout 30 netplan apply 2>/dev/null || log "netplan apply skipped or failed (code $?; 124 = delai de 30 s depasse)"
            ok "Network configuration applied (mode: $mode)"
            ;;
        test)
            # Test WAN connectivity
            if check_link "$wan_iface"; then
                ok "WAN ($wan_iface) has link"
            else
                log "WAN ($wan_iface) no link detected"
            fi
            ;;
        *)
            echo "Usage: $0 {detect|apply|test} [mode]"
            echo "  detect - Detect interfaces and output JSON"
            echo "  apply  - Generate and apply netplan (modes: router|bridge|single)"
            echo "  test   - Test WAN connectivity"
            exit 1
            ;;
    esac
}

main "$@"
