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

PROFILE="${1:-mixed}"
MODELS="${2:-all}"
BASE_DELAY="${3:-0.8}"

WAZUH_SYSLOG_HOST="${WAZUH_SYSLOG_HOST:-127.0.0.1}"
WAZUH_SYSLOG_PORT="${WAZUH_SYSLOG_PORT:-514}"
SIM_MAX_EVENTS="${SIM_MAX_EVENTS:-0}" # 0 = run forever

SIM_SRC_PREFIX="${SIM_SRC_PREFIX:-10.10.20}"
SIM_VPN_USER="${SIM_VPN_USER:-remote.user}"
SIM_ADMIN_USER="${SIM_ADMIN_USER:-admin}"

EVENT_COUNT=0

if ! [[ "${BASE_DELAY}" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
  echo "error: base delay must be numeric (e.g. 0.8)"
  exit 1
fi

if ! [[ "${SIM_MAX_EVENTS}" =~ ^[0-9]+$ ]]; then
  echo "error: SIM_MAX_EVENTS must be an integer >= 0"
  exit 1
fi

emit_syslog() {
  local msg="$1"
  local sent="false"

  if command -v nc >/dev/null 2>&1; then
    if printf "%s\n" "${msg}" | nc -u -w1 "${WAZUH_SYSLOG_HOST}" "${WAZUH_SYSLOG_PORT}"; then
      sent="true"
    fi
  fi

  if [[ "${sent}" != "true" ]]; then
    if printf "%s\n" "${msg}" >"/dev/udp/${WAZUH_SYSLOG_HOST}/${WAZUH_SYSLOG_PORT}" 2>/dev/null; then
      sent="true"
    fi
  fi

  if [[ "${sent}" != "true" ]]; then
    echo "error: failed to send syslog event to ${WAZUH_SYSLOG_HOST}:${WAZUH_SYSLOG_PORT}/udp"
    return 1
  fi

  EVENT_COUNT=$((EVENT_COUNT + 1))
  echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] sent(${EVENT_COUNT}): ${msg}"
}

pick_model() {
  case "${MODELS}" in
    501E|501e) echo "501E" ;;
    80F|80f) echo "80F" ;;
    60F|60f) echo "60F" ;;
    40F|40f) echo "40F" ;;
    all)
      case $((RANDOM % 4)) in
        0) echo "501E" ;;
        1) echo "80F" ;;
        2) echo "60F" ;;
        *) echo "40F" ;;
      esac
      ;;
    *)
      echo "error: unknown models '${MODELS}'"
      echo "valid: 501E | 80F | 60F | 40F | all"
      exit 1
      ;;
  esac
}

devname_for_model() {
  case "$1" in
    501E) echo "FGT501E-HQ" ;;
    80F) echo "FGT80F-Branch01" ;;
    60F) echo "FGT60F-Store12" ;;
    40F) echo "FGT40F-SMB01" ;;
    *) echo "FGT-UNKNOWN" ;;
  esac
}

devid_for_model() {
  case "$1" in
    501E) echo "FGT501E12345678" ;;
    80F) echo "FGT80FTK20000001" ;;
    60F) echo "FGT60FTK21001234" ;;
    40F) echo "FGT40FTK22004567" ;;
    *) echo "FGT00000000000000" ;;
  esac
}

src_ip() {
  echo "${SIM_SRC_PREFIX}.$((RANDOM % 220 + 10))"
}

event_ts() {
  echo "$(date +%s)"
}

syslog_prefix() {
  local pri="$1"
  local model="$2"
  local devname
  local devid
  devname="$(devname_for_model "${model}")"
  devid="$(devid_for_model "${model}")"
  printf "<%s>date=%s time=%s devname=\"%s\" devid=\"%s\" eventtime=%s vd=\"root\"" \
    "${pri}" "$(date '+%Y-%m-%d')" "$(date '+%H:%M:%S')" "${devname}" "${devid}" "$(event_ts)"
}

send_traffic_allow() {
  local model="$1"
  local sip
  sip="$(src_ip)"
  emit_syslog "$(syslog_prefix 189 "${model}") logid=\"0000000013\" type=\"traffic\" subtype=\"forward\" level=\"notice\" srcip=${sip} srcport=$((RANDOM % 40000 + 1024)) srcintf=\"lan\" dstip=172.217.14.$((RANDOM % 200 + 20)) dstport=443 dstintf=\"wan1\" proto=6 service=\"HTTPS\" policyid=12 action=\"accept\" sentbyte=$((RANDOM % 4000 + 500)) rcvdbyte=$((RANDOM % 20000 + 1000)) duration=$((RANDOM % 60 + 2)) soc_mvp_test=true vendor=fortinet product=fortigate model=${model} event_type=fortigate_traffic_allow severity=low"
}

send_webfilter_block() {
  local model="$1"
  local sip
  sip="$(src_ip)"
  emit_syslog "$(syslog_prefix 190 "${model}") logid=\"0419016384\" type=\"utm\" subtype=\"webfilter\" level=\"warning\" srcip=${sip} srcintf=\"internal\" dstip=104.18.$((RANDOM % 200 + 1)).$((RANDOM % 240 + 10)) dstport=80 hostname=\"suspicious-$((RANDOM % 9000 + 1000)).example\" url=\"/dropper\" action=\"blocked\" cat=52 profile=\"WF-Strict\" policyid=3 soc_mvp_test=true vendor=fortinet product=fortigate model=${model} event_type=fortigate_webfilter_block severity=medium"
}

send_ips_block() {
  local model="$1"
  local sip
  sip="$(src_ip)"
  emit_syslog "$(syslog_prefix 190 "${model}") logid=\"0720018432\" type=\"utm\" subtype=\"ips\" level=\"alert\" srcip=${sip} dstip=45.155.$((RANDOM % 200 + 1)).$((RANDOM % 240 + 10)) srcport=$((RANDOM % 40000 + 1024)) dstport=445 sessionid=$((RANDOM % 9000000 + 1000000)) action=\"blocked\" attack=\"MS.SMB.Server.Service.Remote.Code.Execution\" severity=\"critical\" policyid=8 soc_mvp_test=true vendor=fortinet product=fortigate model=${model} event_type=fortigate_ips_block severity=high"
}

send_vpn_down() {
  local model="$1"
  emit_syslog "$(syslog_prefix 191 "${model}") logid=\"0104044546\" type=\"event\" subtype=\"vpn\" level=\"warning\" user=\"${SIM_VPN_USER}\" tunneltype=\"ipsec\" remip=203.0.113.$((RANDOM % 200 + 10)) locip=198.51.100.$((RANDOM % 200 + 10)) action=\"tunnel-down\" reason=\"peer-not-responding\" msg=\"IPsec tunnel Branch-HQ is down\" soc_mvp_test=true vendor=fortinet product=fortigate model=${model} event_type=fortigate_vpn_down severity=high"
}

send_admin_login() {
  local model="$1"
  emit_syslog "$(syslog_prefix 191 "${model}") logid=\"0100032002\" type=\"event\" subtype=\"system\" level=\"information\" logdesc=\"Admin login successful\" user=\"${SIM_ADMIN_USER}\" ui=\"https(10.20.1.$((RANDOM % 200 + 10)))\" msg=\"Administrator ${SIM_ADMIN_USER} logged in successfully\" soc_mvp_test=true vendor=fortinet product=fortigate model=${model} event_type=fortigate_admin_login severity=low"
}

send_event_by_profile() {
  local model="$1"
  local roll
  roll=$((RANDOM % 100))

  case "${PROFILE}" in
    normal)
      if [[ "${roll}" -lt 80 ]]; then
        send_traffic_allow "${model}"
      elif [[ "${roll}" -lt 92 ]]; then
        send_admin_login "${model}"
      elif [[ "${roll}" -lt 98 ]]; then
        send_webfilter_block "${model}"
      else
        send_vpn_down "${model}"
      fi
      ;;
    incident)
      if [[ "${roll}" -lt 45 ]]; then
        send_traffic_allow "${model}"
      elif [[ "${roll}" -lt 70 ]]; then
        send_webfilter_block "${model}"
      elif [[ "${roll}" -lt 92 ]]; then
        send_ips_block "${model}"
      else
        send_vpn_down "${model}"
      fi
      ;;
    mixed)
      if [[ "${roll}" -lt 65 ]]; then
        send_traffic_allow "${model}"
      elif [[ "${roll}" -lt 82 ]]; then
        send_webfilter_block "${model}"
      elif [[ "${roll}" -lt 94 ]]; then
        send_admin_login "${model}"
      elif [[ "${roll}" -lt 98 ]]; then
        send_vpn_down "${model}"
      else
        send_ips_block "${model}"
      fi
      ;;
    *)
      echo "error: unknown profile '${PROFILE}'"
      echo "valid: normal | incident | mixed"
      exit 1
      ;;
  esac
}

echo "starting FortiGate continuous simulator"
echo "profile=${PROFILE} models=${MODELS} base_delay=${BASE_DELAY}s target=${WAZUH_SYSLOG_HOST}:${WAZUH_SYSLOG_PORT}/udp max_events=${SIM_MAX_EVENTS}"
echo "press Ctrl+C to stop"

trap 'echo; echo "stopped after ${EVENT_COUNT} events"; exit 0' INT TERM

while true; do
  send_event_by_profile "$(pick_model)"

  if [[ "${SIM_MAX_EVENTS}" -gt 0 && "${EVENT_COUNT}" -ge "${SIM_MAX_EVENTS}" ]]; then
    echo "done: reached SIM_MAX_EVENTS=${SIM_MAX_EVENTS}"
    exit 0
  fi

  # occasional burst to mimic short real-world spikes
  if [[ $((RANDOM % 100)) -lt 12 ]]; then
    send_event_by_profile "$(pick_model)"
    [[ "${SIM_MAX_EVENTS}" -gt 0 && "${EVENT_COUNT}" -ge "${SIM_MAX_EVENTS}" ]] && exit 0
    send_event_by_profile "$(pick_model)"
    [[ "${SIM_MAX_EVENTS}" -gt 0 && "${EVENT_COUNT}" -ge "${SIM_MAX_EVENTS}" ]] && exit 0
  fi

  sleep "${BASE_DELAY}"
done
