#!/usr/bin/env bash
set -euo pipefail

ACTION="${1:-prepare}"
DOCKER_BIN="${LEARNITIVE_DOCKER_BIN:-/usr/bin/docker}"
IPTABLES_BIN="${LEARNITIVE_IPTABLES_BIN:-/usr/sbin/iptables}"
OWNER="${LEARNITIVE_DEPLOYMENT:-www.learnitive.com}"
ALLOW_WWW_PROMOTION="${LEARNITIVE_ALLOW_WWW_PROMOTION:-0}"
CONFIG_ROOT="${LEARNITIVE_CONFIG_ROOT:-/etc/learnitive}"
APP_ROOT="${LEARNITIVE_APP_ROOT:-/var/www/learnitive.com/public_html}"
MARKER_FILE="$CONFIG_ROOT/.deployment"
RULE_COMMENT='learnitive-os'
OFF_NETWORK='learnitive-os-runtime'
OFF_SUBNET='172.30.242.0/24'
INTERNET_NETWORK='learnitive-os-internet'
INTERNET_SUBNET='172.30.243.0/24'
INTERNET_CHAIN='LEARNITIVE-OS-INTERNET'
LEGACY_INTERNET_CHAINS=()

fail(){ printf '[Learnitive terminal networks] ERROR: %s\n' "$*" >&2; exit 1; }
network_exists(){ "$DOCKER_BIN" network inspect "$1" >/dev/null 2>&1; }
network_label(){ "$DOCKER_BIN" network inspect --format '{{index .Labels "learnitive.deployment"}}' "$1" 2>/dev/null || true; }
network_internal(){ "$DOCKER_BIN" network inspect --format '{{.Internal}}' "$1" 2>/dev/null || true; }
network_subnet(){ "$DOCKER_BIN" network inspect --format '{{(index .IPAM.Config 0).Subnet}}' "$1" 2>/dev/null || true; }
network_icc(){ "$DOCKER_BIN" network inspect --format '{{index .Options "com.docker.network.bridge.enable_icc"}}' "$1" 2>/dev/null || true; }
network_attachments(){ "$DOCKER_BIN" network inspect --format '{{range $id, $_ := .Containers}}{{$id}}{{"\n"}}{{end}}' "$1" 2>/dev/null | sed '/^[[:space:]]*$/d'; }
committed_www_marker_accepts_legacy_owner(){
  local observed="${1:-}"
  [[ "$OWNER" == 'www.learnitive.com' ]] || return 1
  [[ "$observed" == 'edu.learnitive.com' || "$observed" == 'os.learnitive.com' ]] || return 1
  [[ -f "$MARKER_FILE" && ! -L "$MARKER_FILE" ]] || return 1
  [[ "$(stat -c %U:%G "$MARKER_FILE" 2>/dev/null || true)" == 'root:root' ]] || return 1
  grep -Fxq 'domain=www.learnitive.com' "$MARKER_FILE" || return 1
  grep -Fxq "root=$APP_ROOT" "$MARKER_FILE" || return 1
  grep -Fxq "network_owner_compat=$observed" "$MARKER_FILE"
}

network_owner_is_accepted(){
  local observed="${1:-}"
  [[ "$observed" == "$OWNER" ]] && return 0
  if committed_www_marker_accepts_legacy_owner "$observed"; then return 0; fi
  [[ "$OWNER" == 'www.learnitive.com' && "$ALLOW_WWW_PROMOTION" == '1' ]] || return 1
  [[ "$observed" == 'edu.learnitive.com' || "$observed" == 'os.learnitive.com' ]]
}

verify_network(){
  local name="$1" subnet="$2" internal="$3"
  network_owner_is_accepted "$(network_label "$name")" || fail "$name is not owned by $OWNER."
  [[ "$(network_subnet "$name")" == "$subnet" ]] || fail "$name uses unexpected subnet $(network_subnet "$name")."
  [[ "$(network_internal "$name")" == "$internal" ]] || fail "$name internal flag differs from expected $internal."
  [[ "$(network_icc "$name")" == 'false' ]] || fail "$name must have inter-container communication disabled."
}

ensure_network(){
  local name="$1" subnet="$2" internal="$3"
  if network_exists "$name"; then
    if network_owner_is_accepted "$(network_label "$name")"; then verify_network "$name" "$subnet" "$internal"; return; fi
    fail "$name already exists but is not owned by Learnitive deployment $OWNER; refusing to adopt, remove, or recreate it."
  fi
  local args=(network create --driver bridge --subnet "$subnet" --opt com.docker.network.bridge.enable_icc=false --label "learnitive.deployment=$OWNER")
  [[ "$internal" == true ]] && args+=(--internal)
  args+=("$name")
  "$DOCKER_BIN" "${args[@]}" >/dev/null
  verify_network "$name" "$subnet" "$internal"
}

normalize_rule(){ sed -E -e "s/[[:space:]]+-m comment --comment \"?$RULE_COMMENT\"?//g" -e 's/[[:space:]]+-m (tcp|udp|icmp)//g' -e 's/[[:space:]]+--reject-with icmp-port-unreachable//g' -e 's/[[:space:]]+/ /g' -e 's/[[:space:]]+$//'; }
expected_internet_rules(){
  local cidr
  for cidr in 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do printf '%s\n' "-A $INTERNET_CHAIN -d $cidr -j REJECT"; done
  printf '%s\n' "-A $INTERNET_CHAIN -j ACCEPT"
}
actual_internet_rules(){ "$IPTABLES_BIN" -S "$INTERNET_CHAIN" 2>/dev/null | while IFS= read -r line; do [[ "$line" == -A\ * ]] || continue; printf '%s\n' "$line" | normalize_rule; done; }
internet_chain_all_rules_owned(){
  local line saw=0
  while IFS= read -r line; do
    [[ "$line" == -A\ * ]] || continue
    saw=1
    [[ "$line" == *"--comment $RULE_COMMENT"* || "$line" == *"--comment \"$RULE_COMMENT\""* ]] || return 1
  done < <("$IPTABLES_BIN" -S "$INTERNET_CHAIN")
  [[ "$saw" == 1 ]]
}
verify_internet_chain(){
  "$IPTABLES_BIN" -S "$INTERNET_CHAIN" >/dev/null 2>&1 || return 0
  internet_chain_all_rules_owned || fail "Existing $INTERNET_CHAIN contains unowned rules; refusing to adopt or rewrite it."
  [[ "$(actual_internet_rules)" == "$(expected_internet_rules)" ]] || fail "Existing owned $INTERNET_CHAIN differs from the expected private-network-blocking profile."
}
verify_internet_hooks(){
  "$IPTABLES_BIN" -S DOCKER-USER >/dev/null 2>&1 || fail 'Docker DOCKER-USER chain is unavailable.'
  local line normalized
  while IFS= read -r line; do
    [[ "$line" == -A\ DOCKER-USER*"-j $INTERNET_CHAIN"* ]] || continue
    [[ "$line" == *"--comment $RULE_COMMENT"* || "$line" == *"--comment \"$RULE_COMMENT\""* ]] || fail "DOCKER-USER contains an unowned jump to $INTERNET_CHAIN; refusing to adopt or rewrite it. Observed: $line"
    normalized="$(printf '%s\n' "$line" | normalize_rule)"
    [[ "$normalized" == "-A DOCKER-USER -s $INTERNET_SUBNET -j $INTERNET_CHAIN" ]] || fail "Unexpected owned hook into $INTERNET_CHAIN: $line"
  done < <("$IPTABLES_BIN" -S DOCKER-USER)
}
legacy_internet_expected_rules(){
  local legacy_chain="$1"
  expected_internet_rules | sed "s/$INTERNET_CHAIN/$legacy_chain/g"
}
legacy_internet_actual_rules(){
  local legacy_chain="$1"
  "$IPTABLES_BIN" -S "$legacy_chain" 2>/dev/null | while IFS= read -r line; do
    [[ "$line" == -A\ * ]] || continue
    printf '%s\n' "$line" | normalize_rule
  done
}
legacy_internet_chain_exists(){
  local legacy_chain="$1" listing
  listing="$("$IPTABLES_BIN" -S "$legacy_chain" 2>/dev/null || true)"
  printf '%s\n' "$listing" | grep -E -- "^-N ${legacy_chain}$|^-A ${legacy_chain} " >/dev/null
}
retire_recognized_legacy_internet_chain(){
  local legacy_chain="$1" line normalized
  legacy_internet_chain_exists "$legacy_chain" || return 0
  [[ "$(legacy_internet_actual_rules "$legacy_chain")" == "$(legacy_internet_expected_rules "$legacy_chain")" ]] ||     fail "Legacy firewall chain $legacy_chain exists but does not exactly match the recognized Learnitive Internet profile; refusing to remove it."
  while IFS= read -r line; do
    [[ "$line" == -A\ DOCKER-USER*"-j $legacy_chain"* ]] || continue
    if [[ "$line" == *'--comment '* && "$line" != *"--comment $RULE_COMMENT"* && "$line" != *"--comment \"$RULE_COMMENT\""* ]]; then
      fail "Legacy firewall chain $legacy_chain has a foreign-commented DOCKER-USER hook; refusing to remove it. Observed: $line"
    fi
    normalized="$(printf '%s\n' "$line" | normalize_rule)"
    [[ "$normalized" == "-A DOCKER-USER -s $INTERNET_SUBNET -j $legacy_chain" ]] ||       fail "Legacy firewall chain $legacy_chain has an unrecognized DOCKER-USER hook; refusing to remove it. Observed: $line"
  done < <("$IPTABLES_BIN" -S DOCKER-USER)
  while "$IPTABLES_BIN" -C DOCKER-USER -s "$INTERNET_SUBNET" -m comment --comment "$RULE_COMMENT" -j "$legacy_chain" 2>/dev/null; do
    "$IPTABLES_BIN" -D DOCKER-USER -s "$INTERNET_SUBNET" -m comment --comment "$RULE_COMMENT" -j "$legacy_chain"
  done
  while "$IPTABLES_BIN" -C DOCKER-USER -s "$INTERNET_SUBNET" -j "$legacy_chain" 2>/dev/null; do
    "$IPTABLES_BIN" -D DOCKER-USER -s "$INTERNET_SUBNET" -j "$legacy_chain"
  done
  if "$IPTABLES_BIN" -S DOCKER-USER 2>/dev/null | grep -F -- "-j $legacy_chain" >/dev/null; then
    fail "Legacy firewall chain $legacy_chain still has a DOCKER-USER reference after recognized hook cleanup."
  fi
  printf '[Learnitive terminal networks] Retiring recognized legacy firewall chain %s.\n' "$legacy_chain"
  "$IPTABLES_BIN" -F "$legacy_chain"
  "$IPTABLES_BIN" -X "$legacy_chain"
}
retire_recognized_legacy_internet_chains(){
  local legacy_chain
  for legacy_chain in "${LEGACY_INTERNET_CHAINS[@]}"; do retire_recognized_legacy_internet_chain "$legacy_chain"; done
}
prepare_internet_firewall(){
  verify_internet_chain
  if "$IPTABLES_BIN" -S "$INTERNET_CHAIN" >/dev/null 2>&1; then "$IPTABLES_BIN" -F "$INTERNET_CHAIN"; else "$IPTABLES_BIN" -N "$INTERNET_CHAIN"; fi
  local cidr
  for cidr in 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do
    "$IPTABLES_BIN" -A "$INTERNET_CHAIN" -d "$cidr" -m comment --comment "$RULE_COMMENT" -j REJECT
  done
  "$IPTABLES_BIN" -A "$INTERNET_CHAIN" -m comment --comment "$RULE_COMMENT" -j ACCEPT
  verify_internet_hooks
  "$IPTABLES_BIN" -C DOCKER-USER -s "$INTERNET_SUBNET" -m comment --comment "$RULE_COMMENT" -j "$INTERNET_CHAIN" 2>/dev/null || \
    "$IPTABLES_BIN" -I DOCKER-USER 1 -s "$INTERNET_SUBNET" -m comment --comment "$RULE_COMMENT" -j "$INTERNET_CHAIN"
}

check(){
  "$DOCKER_BIN" info >/dev/null 2>&1 || fail 'Docker unavailable.'
  if network_exists "$OFF_NETWORK"; then verify_network "$OFF_NETWORK" "$OFF_SUBNET" true; fi
  if network_exists "$INTERNET_NETWORK"; then verify_network "$INTERNET_NETWORK" "$INTERNET_SUBNET" false; fi
  verify_internet_chain
  verify_internet_hooks
}
prepare(){
  "$DOCKER_BIN" info >/dev/null 2>&1 || fail 'Docker unavailable.'
  ensure_network "$OFF_NETWORK" "$OFF_SUBNET" true
  ensure_network "$INTERNET_NETWORK" "$INTERNET_SUBNET" false
  prepare_internet_firewall
}

case "$ACTION" in
  check) check ;;
  prepare) prepare ;;
  *) fail 'Usage: terminal-network-control.sh check|prepare' ;;
esac
