#!/bin/bash
set -euo pipefail

update_bridge_forward_rules()
{
   local lan_if="$1"
   local bridge_if="$2"
   local subnet="$3"

   sudo iptables -C FORWARD -i "$lan_if" -o "$bridge_if" -d "$subnet" -j ACCEPT 2>/dev/null || \
      sudo iptables -A FORWARD -i "$lan_if" -o "$bridge_if" -d "$subnet" -j ACCEPT

   sudo iptables -C FORWARD -i "$bridge_if" -o "$lan_if" -s "$subnet" -j ACCEPT 2>/dev/null || \
      sudo iptables -A FORWARD -i "$bridge_if" -o "$lan_if" -s "$subnet" -j ACCEPT
}

remove_bridge_forward_rules()
{
   local lan_if="$1"
   local bridge_if="$2"
   local subnet="$3"

   while sudo iptables -C FORWARD -i "$lan_if" -o "$bridge_if" -d "$subnet" -j ACCEPT 2>/dev/null
   do
      sudo iptables -D FORWARD -i "$lan_if" -o "$bridge_if" -d "$subnet" -j ACCEPT
   done

   while sudo iptables -C FORWARD -i "$bridge_if" -o "$lan_if" -s "$subnet" -j ACCEPT 2>/dev/null
   do
      sudo iptables -D FORWARD -i "$bridge_if" -o "$lan_if" -s "$subnet" -j ACCEPT
   done
}

update_forward_rules()
{
   local lan_if="$1"
   local host_if="$2"
   local subnet="$3"

   sudo iptables -C FORWARD -i "$lan_if" -o "$host_if" -d "$subnet" -j ACCEPT 2>/dev/null || \
      sudo iptables -A FORWARD -i "$lan_if" -o "$host_if" -d "$subnet" -j ACCEPT

   sudo iptables -C FORWARD -i "$host_if" -o "$lan_if" -s "$subnet" -j ACCEPT 2>/dev/null || \
      sudo iptables -A FORWARD -i "$host_if" -o "$lan_if" -s "$subnet" -j ACCEPT
}

remove_forward_rules()
{
   local lan_if="$1"
   local host_if="$2"
   local subnet="$3"

   while sudo iptables -C FORWARD -i "$lan_if" -o "$host_if" -d "$subnet" -j ACCEPT 2>/dev/null
   do
      sudo iptables -D FORWARD -i "$lan_if" -o "$host_if" -d "$subnet" -j ACCEPT
   done

   while sudo iptables -C FORWARD -i "$host_if" -o "$lan_if" -s "$subnet" -j ACCEPT 2>/dev/null
   do
      sudo iptables -D FORWARD -i "$host_if" -o "$lan_if" -s "$subnet" -j ACCEPT
   done
}

update_bridge_host_rules()
{
   local subnet="$1"
   local bridge_if="$2"

   sudo iptables -C OUTPUT -o "$bridge_if" -d "$subnet" -j ACCEPT 2>/dev/null || \
      sudo iptables -A OUTPUT -o "$bridge_if" -d "$subnet" -j ACCEPT

   sudo iptables -C INPUT -i "$bridge_if" -s "$subnet" -j ACCEPT 2>/dev/null || \
      sudo iptables -A INPUT -i "$bridge_if" -s "$subnet" -j ACCEPT
}

remove_bridge_host_rules()
{
   local subnet="$1"
   local bridge_if="$2"

   while sudo iptables -C OUTPUT -o "$bridge_if" -d "$subnet" -j ACCEPT 2>/dev/null
   do
      sudo iptables -D OUTPUT -o "$bridge_if" -d "$subnet" -j ACCEPT
   done

   while sudo iptables -C INPUT -i "$bridge_if" -s "$subnet" -j ACCEPT 2>/dev/null
   do
      sudo iptables -D INPUT -i "$bridge_if" -s "$subnet" -j ACCEPT
   done
}

invalid_cidr()
{
   local cidr="$1"

   if [[ ! $cidr =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]{1,2})$ ]]; then
      return 0
   fi

   local ip="${cidr%/*}"
   local prefix="${cidr#*/}"

   if (( prefix < 0 || prefix > 32 )); then
      return 0
   fi

   IFS='.' read -r o1 o2 o3 o4 <<< "$ip"

   for octet in "$o1" "$o2" "$o3" "$o4"
   do
      if (( octet < 0 || octet > 255 )); then
         return 0
      fi
   done

   return 1
}

show_usage()
{
   cat <<EOF
Create or remove a Docker network and optionally configure host access.

Usage:
   $0 [-t <ntype>] [-s <subnet>] [-g <gateway>] [-n <nname>] [-i] [--link_ip=<link ip>] [--bridge-name=<bridge ifname>]
   $0 -r -n <nname>

Where:
   -t <ntype>          Network type: bridge, macvlan, ipvlan (def=${ntype})
   -s <subnet>         Subnet in CIDR format (def=${sn})
   -g <gateway>        Gateway IP address (def=${gw})
   -n <nname>          Docker network name (def=${nname})
   -i                  Configure host/routed access
   -r                  Remove/cleanup the given network
   --link_ip=<cidr>    Host-side helper IP for macvlan/ipvlan (def=${lip})
   --bridge-name=<if>  Linux bridge interface name for bridge networks (optional, otherwise br-nnnnnnnnnnnn)

Notes:
   * For macvlan/ipvlan, -g should normally be the real upstream router.
   * For bridge, -g is the Docker bridge gateway.
   * For bridge, -i adds INPUT/OUTPUT rules for host access and FORWARD rules for routed LAN access.
   * In remove mode, the network name is required.
EOF
}

derive_bridge_ifname()
{
   local network_name="$1"
   local explicit_bridge_name="$2"
   local bridge_if

   if [ -n "$explicit_bridge_name" ]; then
      echo "$explicit_bridge_name"
      return 0
   fi

   bridge_if=$(docker network inspect "$network_name" -f '{{index .Options "com.docker.network.bridge.name"}}' 2>/dev/null || true)
   if [ -n "$bridge_if" ] && [ "$bridge_if" != "<no value>" ]; then
      echo "$bridge_if"
      return 0
   fi

   local netid
   netid=$(docker network inspect "$network_name" -f '{{.Id}}')
   echo "br-${netid:0:12}"
}

network_has_endpoints()
{
   local network_name="$1"
   local count

   count=$(docker network inspect "$network_name" -f '{{len .Containers}}' 2>/dev/null || echo "0")
   [ "$count" != "0" ]
}

remove_network()
{
   local network_name="$1"
   local host_if_name="${network_name}_host"

   if ! docker network inspect "$network_name" >/dev/null 2>&1; then
      echo "Docker network $network_name does not exist"
      exit 1
   fi

   local driver
   local subnet
   local bridge_if

   driver=$(docker network inspect "$network_name" -f '{{.Driver}}')
   subnet=$(docker network inspect "$network_name" -f '{{(index .IPAM.Config 0).Subnet}}')

   echo "Removing Docker network $network_name"
   echo "  Driver: $driver"
   echo "  Subnet: $subnet"

   case "$driver" in
      bridge)
         bridge_if=$(derive_bridge_ifname "$network_name" "$bridge_name")

         if ip link show "$bridge_if" >/dev/null 2>&1; then
            echo "Removing bridge INPUT/OUTPUT rules for $bridge_if"
            remove_bridge_host_rules "$subnet" "$bridge_if"

            echo "Removing bridge FORWARD rules for $bridge_if"
            remove_bridge_forward_rules "$interf" "$bridge_if" "$subnet"
         fi
         ;;

      macvlan|ipvlan)
         if ip link show "$host_if_name" >/dev/null 2>&1; then
            echo "Removing FORWARD rules for helper interface $host_if_name"
            remove_forward_rules "$interf" "$host_if_name" "$subnet"

            echo "Deleting helper interface $host_if_name"
            sudo ip link delete "$host_if_name"
         fi
         ;;
   esac

   if network_has_endpoints "$network_name"; then
      echo "ERROR: Network $network_name still has attached containers/endpoints"
      echo "Detach or remove them first, then rerun."
      exit 1
   fi

   docker network rm "$network_name"
   echo "Removed Docker network $network_name"
}

# Defaults
ntype="macvlan"
sn="192.168.2.0/24"
gw="192.168.2.1"
interf=$(ip route get 8.8.8.8 | awk '{for(i=1;i<=NF;i++) if ($i=="dev") print $(i+1)}')
nname="corp_lan"
configure_host_access=false
remove_mode=false
lname="${nname}_host"
lip="192.168.2.10/24"
bridge_name=""

# Process options
while getopts "h?t:s:g:n:ir-:" opt
do
   case $opt in
      t )
         ntype=$OPTARG
         ;;
      s )
         sn=$OPTARG
         ;;
      g )
         gw=$OPTARG
         ;;
      n )
         nname=$OPTARG
         lname="${nname}_host"
         ;;
      i )
         configure_host_access=true
         ;;
      r )
         remove_mode=true
         ;;
      - )
         case ${OPTARG} in
            link_ip=* )
               lip="${OPTARG#*=}"
               ;;
            bridge-name=* )
               bridge_name="${OPTARG#*=}"
               ;;
            * )
               echo "Unknown option: --${OPTARG}"
               show_usage
               exit 1
               ;;
         esac
         ;;
      h | \? )
         show_usage
         exit 1
         ;;
   esac
done
shift $((OPTIND - 1))

if [ "$remove_mode" = true ]; then
   if [ -z "$nname" ]; then
      echo "ERROR: Remove mode requires -n <network_name>"
      exit 1
   fi

   remove_network "$nname"
   exit 0
fi

# Validate inputs
if invalid_cidr "$sn"; then
   echo "ERROR: Invalid CIDR IPv4 format: $sn"
   exit 1
fi

if [ "$configure_host_access" = true ] && [ "$ntype" != "bridge" ] && invalid_cidr "$lip"; then
   echo "ERROR: Invalid CIDR IPv4 format: $lip"
   exit 1
fi

case "$ntype" in
   bridge|macvlan|ipvlan)
      ;;
   *)
      echo "ERROR: Unsupported network type: $ntype"
      exit 1
      ;;
esac

# Ensure Docker network
if docker network inspect "$nname" >/dev/null 2>&1; then
   echo "Docker network $nname already exists"
else
   echo "Creating Docker network $nname"

   if [ "$ntype" = "bridge" ]; then
      create_cmd=(docker network create -d bridge --subnet="$sn" --gateway="$gw")

      if [ -n "$bridge_name" ]; then
         create_cmd+=(-o "com.docker.network.bridge.name=$bridge_name")
      fi

      if [ "$configure_host_access" = true ]; then
         create_cmd+=(-o "com.docker.network.bridge.trusted_host_interfaces=$interf")
      fi

      create_cmd+=("$nname")
      "${create_cmd[@]}"
   else
      docker network create \
         -d "$ntype" \
         --subnet="$sn" \
         --gateway="$gw" \
         -o parent="$interf" \
         "$nname"
   fi
fi

# Host-access configuration
if [ "$configure_host_access" = true ]; then
   if [ "$ntype" = "bridge" ]; then
      bridge_if=$(derive_bridge_ifname "$nname" "$bridge_name")

      if ! ip link show "$bridge_if" >/dev/null 2>&1; then
         echo "ERROR: Derived bridge interface $bridge_if does not exist"
         exit 1
      fi

      echo "Configuring host firewall rules for bridge interface $bridge_if"
      update_bridge_host_rules "$sn" "$bridge_if"
      update_bridge_forward_rules "$interf" "$bridge_if" "$sn"
   else
      if ip link show "$lname" >/dev/null 2>&1; then
         echo "Link $lname already exists... removing"
         sudo ip link delete "$lname"
      fi

      echo "Creating host ${ntype} link $lname"
      sudo ip link add "$lname" link "$interf" type "$ntype" mode bridge

      if ip addr show "$lname" | grep -q "$lip"; then
         echo "IP $lip already assigned to $lname"
      else
         echo "Assigning IP $lip to $lname"
         sudo ip addr add "$lip" dev "$lname"
      fi

      if ip link show "$lname" | grep -q "state UP"; then
         echo "$lname already UP"
      else
         echo "Bringing $lname up"
         sudo ip link set "$lname" up
      fi

      update_forward_rules "$interf" "$lname" "$sn"
   fi
fi
