#!/bin/bash
# =============================================================================
#  m2ctl -- operator conveniences inside the game container.
#
#    docker compose exec game m2ctl status
#    docker compose exec game m2ctl ports
#    docker compose exec game m2ctl config ch1-first
#    docker compose exec game m2ctl syserr
#    docker compose exec game m2ctl logsize
#    docker compose exec game m2ctl prune-logs [days]
# =============================================================================
set -uo pipefail

VAR_DIR="${M2_VAR_DIR:-/opt/metin2/var}"
PLAN="$VAR_DIR/boot-plan"

port_open() { (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null; }

cmd_status() {
  printf '%-14s %-8s %-34s %s\n' CORE PORT DIRECTORY STATE
  while IFS='|' read -r name dir _bin port _kind; do
    [ -n "$name" ] || continue
    if port_open "$port"; then state="listening"; else state="DOWN"; fi
    printf '%-14s %-8s %-34s %s\n' "$name" "$port" "$dir" "$state"
  done < "$PLAN"
}

cmd_ports() {
  # ss/netstat are not in the runtime image; /proc/net/tcp always is.
  # Field 2 is local_address as HEX_IP:HEX_PORT with the IP in little-endian
  # byte order; field 4 is the connection state, 0A = LISTEN.
  #
  # Done in bash rather than awk on purpose: strtonum() is a GNU awk extension
  # and the base image ships mawk, where it is simply an undefined function.
  echo "listening TCP sockets inside the container:"
  {
    read -r _header
    while read -r _sl local _rem state _rest; do
      [ "$state" = "0A" ] || continue
      hexip=${local%%:*}
      hexport=${local##*:}
      printf '  %-16s %d\n' \
        "$(printf '%d.%d.%d.%d' "0x${hexip:6:2}" "0x${hexip:4:2}" "0x${hexip:2:2}" "0x${hexip:0:2}")" \
        "0x$hexport"
    done
  } < /proc/net/tcp | sort -k2 -n -u
}

cmd_config() {
  local want="${1:-}"
  while IFS='|' read -r name dir _bin _port kind; do
    [ -n "$name" ] || continue
    [ -z "$want" ] || [ "$want" = "$name" ] || continue
    local f="$dir/CONFIG"; [ "$kind" = "db" ] && f="$dir/conf.txt"
    echo "===== $name -- $f"
    # The rendered files carry the database password; keep it out of a paste.
    sed -E 's/(SQL[A-Z_]*[:=][^"]*")[^ ]+ [^ ]+ ([^ ]+)/\1<user> <password> \2/; s/(_SQL:[[:space:]]*[^[:space:]]+[[:space:]]+)[^[:space:]]+[[:space:]]+[^[:space:]]+/\1<user> <password>/' "$f"
    echo
  done < "$PLAN"
}

cmd_syserr() {
  while IFS='|' read -r name dir _bin _port _kind; do
    [ -n "$name" ] || continue
    # `grep -c' prints 0 AND exits non-zero when it matches nothing, so a
    # `|| echo 0' fallback appends a second line and the count comes out as
    # "0\n0".  Normalise instead of guarding.
    local n real
    n=$(grep -c SYSERR "$dir/syserr" 2>/dev/null); n=${n:-0}
    real=$(grep SYSERR "$dir/syserr" 2>/dev/null | grep -cvE 'pid_init|pid_deinit|TABLE_POSTFIX|hupsig'); real=${real:-0}
    printf '%-14s %5s SYSERR lines (%s after removing known-benign startup noise)\n' "$name" "$n" "$real"
  done < "$PLAN"
  echo
  echo "last 20 non-benign lines:"
  while IFS='|' read -r name dir _bin _port _kind; do
    [ -n "$name" ] || continue
    grep SYSERR "$dir/syserr" 2>/dev/null | grep -vE 'pid_init|pid_deinit|TABLE_POSTFIX|hupsig' \
      | tail -20 | sed "s|^|  [$name] |"
  done < "$PLAN"
}

cmd_logsize() {
  echo "state volume usage (a busy channel core writes ~40 MB of syslog per hour):"
  du -sh "$VAR_DIR" 2>/dev/null
  while IFS='|' read -r name dir _bin _port _kind; do
    [ -n "$name" ] || continue
    printf '  %-14s %s\n' "$name" "$(du -sh "$dir" 2>/dev/null | cut -f1)"
  done < "$PLAN"
}

cmd_prune_logs() {
  local days="${1:-${M2_LOG_KEEP_DAYS:-7}}"
  echo "removing rotated log directories older than ${days} day(s)"
  while IFS='|' read -r name dir _bin _port _kind; do
    [ -n "$name" ] || continue
    find "$dir/log" -mindepth 1 -maxdepth 1 -type d -mtime "+${days}" \
         -exec rm -rf {} + 2>/dev/null && echo "  pruned $name"
  done < "$PLAN"
  cmd_logsize
}

[ -r "$PLAN" ] || { echo "no boot plan at $PLAN -- the container has not finished starting"; exit 1; }

case "${1:-status}" in
  status)     cmd_status ;;
  ports)      cmd_ports ;;
  config)     shift; cmd_config "${1:-}" ;;
  syserr)     cmd_syserr ;;
  logsize)    cmd_logsize ;;
  prune-logs) shift; cmd_prune_logs "${1:-}" ;;
  *) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 1 ;;
esac
