#!/usr/bin/env bash

if [ "$MODE" = "debug" ]; then
  set -x
fi
caller=${caller:-"user"}
password=${password:-""}
__file="$(basename "${BASH_SOURCE[0]}")"

usage() {
  cat <<EOF

Usage: $__file COMMAND

Commands:
    $__file start           Start DLM service
    $__file stop            Stop DLM service
    $__file restart         Restart DLM service
    $__file status          Display DLM service status
    $__file logs            Display DLM service logs
    $__file backup          Backup DLM service
    $__file restore         Restore DLM service
    $__file verify-backup   Verify backup of DLM service
    $__file diagnostic      Run diagnostic tool
    $__file version         Print the DLM cli version information

EOF
  exit
}

setup_log_dir() {
  mkdir -p /var/log/dlm
  touch /var/log/dlm/install.log
  touch /var/log/dlm/cli.log
  chmod 644 -R /var/log/dlm/*
  chown -R syslog:adm /var/log/dlm
}

func_cli_log() {
  if [ ! -d /var/log/dlm ]; then
    setup_log_dir
  fi
  logger -t dlm-cli "[$(whoami) $(version) $caller] $1"
}

func_docker_openssl() {
  docker run -i --rm \
    -v /dev/urandom:/dev/random \
    -v /opt/moxa/dlm:/opt/moxa/dlm:rw \
    -v "${USER_DATA_DIR}:${USER_DATA_DIR}:rw" \
    -v "$PWD":"$PWD" \
    -w "$PWD" \
    --user root \
    ${REGISTRY_HOST}/openssl:${OPENSSL_VERSION} "$@"
}

func_check_cert_issuer() {
  local cert_file="$1"
  if [ ! -f "$cert_file" ]; then
    echo "not_found"
    return
  fi

  # Check if certificate is issued by Let's Encrypt
  local issuer=$(func_docker_openssl x509 -in "$cert_file" -noout -issuer | grep -i "let's encrypt")
  if [ -n "$issuer" ]; then
    echo "letsencrypt"
  else
    echo "other"
  fi
}

func_check_cert_expiry() {
  local cert_file="$1"
  if [ ! -f "$cert_file" ]; then
    echo "not_found"
    return
  fi

  # Get certificate expiry date in seconds since epoch
  local expiry_date=$(func_docker_openssl x509 -in "$cert_file" -noout -dates | grep notAfter | cut -d= -f2)
  local expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null || echo "0")
  local current_epoch=$(date +%s)
  local seven_days_epoch=$((7 * 24 * 60 * 60))
  local days_until_expiry=$(( (expiry_epoch - current_epoch) / (24 * 60 * 60) ))

  if [ "$expiry_epoch" -le "$current_epoch" ]; then
    echo "expired"
  elif [ $((expiry_epoch - current_epoch)) -le $seven_days_epoch ]; then
    echo "expiring_soon"
  else
    echo "valid"
  fi
}

func_docker_certbot() {
  docker run --rm \
    -v "/var/lib/dlm/data/certs/certbot:/etc/letsencrypt" \
    -v "/var/lib/dlm/data/certs/certbot/work:/var/lib/letsencrypt" \
    -v "/var/log/dlm/certbot:/var/log/letsencrypt" \
    -p 80:80 \
    certbot/certbot "$@"
}

certbot_obtain_certificate() {
  func_cli_log "Starting certbot certificate management"

  # Check current certificate status
  local cert_file="/var/lib/dlm/data/certs/web/cert.pem"
  local cert_issuer=$(func_check_cert_issuer "$cert_file")
  local cert_status=$(func_check_cert_expiry "$cert_file")

  func_cli_log "Current certificate issuer: $cert_issuer, status: $cert_status"

  # Determine if we need to obtain/renew certificate
  local need_certificate=false

  if [ "$cert_issuer" = "not_found" ]; then
    func_cli_log "No certificate found, obtaining new certificate"
    need_certificate=true
  elif [ "$cert_issuer" != "letsencrypt" ]; then
    func_cli_log "Current certificate is not from Let's Encrypt, replacing with Let's Encrypt certificate"
    need_certificate=true
  elif [ "$cert_status" = "expired" ] || [ "$cert_status" = "expiring_soon" ]; then
    func_cli_log "Let's Encrypt certificate is expired or expiring soon, renewing"
    need_certificate=true
  else
    func_cli_log "Let's Encrypt certificate is valid, no action needed"
    echo "Certificate is valid and not expiring soon. No action needed."
    return 0
  fi

  if [ "$need_certificate" = "false" ]; then
    return 0
  fi

  # Read domain and email from global.env
  local domain=$(config global SERVICE_FQDN)
  local admin_email=$(config global ADMIN_EMAIL)

  if [ -z "$domain" ]; then
    echo "Error: SERVICE_FQDN not found in global.env"
    func_cli_log "Error: SERVICE_FQDN not found in global.env"
    return 1
  fi

  if [ -z "$admin_email" ]; then
    echo "Error: ADMIN_EMAIL not found in global.env"
    func_cli_log "Error: ADMIN_EMAIL not found in global.env"
    return 1
  fi

  func_cli_log "Using domain: $domain, email: $admin_email"

  # Stop DLM service
  func_cli_log "Stopping DLM service for certificate generation"
  stop

  # Create certbot directories
  mkdir -p "/var/lib/dlm/data/certs/certbot/config"
  mkdir -p "/var/lib/dlm/data/certs/certbot/work"
  mkdir -p "/var/lib/dlm/data/certs/certbot/logs"

  # Run certbot to obtain certificate
  func_cli_log "Running certbot to obtain certificate for domain: $domain"
  if func_docker_certbot certonly \
    --standalone \
    --non-interactive \
    --agree-tos \
    --email "$admin_email" \
    -d "$domain" \
    --config-dir "/etc/letsencrypt" \
    --work-dir "/var/lib/letsencrypt" \
    --logs-dir "/var/log/letsencrypt"; then

    func_cli_log "Certificate obtained successfully"

    # Create symbolic links to the new certificates
    local cert_live_dir="/var/lib/dlm/data/certs/certbot/live/$domain"

    if [ -d "$cert_live_dir" ]; then
      func_cli_log "Creating symbolic links for web certificates"
      # Backup existing web certificates if they exist
      for cert_file in cert.pem privkey.pem fullchain.pem; do
        if [ -f "/var/lib/dlm/data/certs/web/$cert_file" ] && [ ! -L "/var/lib/dlm/data/certs/web/$cert_file" ]; then
          cp "/var/lib/dlm/data/certs/web/$cert_file" "/var/lib/dlm/data/certs/web/$cert_file.backup.$(date +%Y%m%d%H%M%S)"
          func_cli_log "Backed up existing web/$cert_file"
        fi
      done
      ln -sf "../certbot/live/$domain/cert.pem" "/var/lib/dlm/data/certs/web/cert.pem"
      ln -sf "../certbot/live/$domain/privkey.pem" "/var/lib/dlm/data/certs/web/privkey.pem"
      ln -sf "../certbot/live/$domain/fullchain.pem" "/var/lib/dlm/data/certs/web/fullchain.pem"

      func_cli_log "Let's Encrypt certificates installed for web services"
      echo "Certificate obtained and installed successfully for domain: $domain"
    else
      func_cli_log "Error: Certificate directory not found after certbot execution"
      echo "Error: Certificate was not generated properly"
      start
      return 1
    fi
  else
    func_cli_log "Error: Certbot failed to obtain certificate"
    echo "Error: Failed to obtain certificate. Check logs for details."
    start
    return 1
  fi

  # Start DLM service
  func_cli_log "Starting DLM service with new certificates"
  start

  func_cli_log "Certbot certificate management completed successfully"
  echo "Certificate management completed successfully"
}

source_environment_variables() {
  # get the directory of the script
  install_dir=$(dirname "$(dirname "$(readlink -f "$0")")")
  export install_dir
  if [ -f "${install_dir}/static-config/env-files/manifest.env" ]; then
    # shellcheck disable=SC2046
    export $(grep -v '^#' "${install_dir}/static-config/env-files/manifest.env" | xargs)
  fi
  if [ -f "${USER_DATA_DIR}/env-files/global.env" ]; then
    # shellcheck disable=SC2046
    export $(grep -v '^#' "${USER_DATA_DIR}/env-files/global.env" | xargs)
  fi
}

# usage:
# set value: config scope key value
# get value: config scope key
config() {
  local scope=$1
  # if scope is global or migration then path is $USER_DATA_DIR/env-files/${scope}.env"
  local file="$install_dir/static-config/env-files/${scope}.env"
  user_scope=("install" "global" "migration")
  for s in "${user_scope[@]}"; do
    if [ "$scope" = "$s" ]; then
      local file="$USER_DATA_DIR/env-files/${scope}.env"
      break
    fi
  done
  local key=$2
  local value=$3

  if [ -f "$file" ]; then
    if [ -n "$key" ]; then
      if [ -n "$value" ]; then
        if [ "$value" = "remove" ]; then
          # remove config
          if grep -q "^$key=" "$file"; then
            func_cli_log "Removing $key from $file"
            # if gsed exists, use it
            if [ -x "$(command -v gsed)" ]; then
              gsed -i "/^$key=/d" "$file"
            else
              sed -i "/^$key=/d" "$file"
            fi
          else
            func_cli_log "Key $key not found in $file"
          fi
        elif ! grep -q "^$key=" "$file"; then
          echo "$key=$value" >>"$file"
        else
          # if key includes SECRET, do not log the value
          if [[ "$key" == *"SECRET"* ]]; then
            func_cli_log "Updating $key=****** in $file"
          else
            func_cli_log "Updating $key=$value in $file"
          fi
          # if gsed exists, use it
          if [ -x "$(command -v gsed)" ]; then
            gsed -i "s#^$key=.*#$key=$value#" "$file"
          else
            sed -i "s#^$key=.*#$key=$value#" "$file"
          fi
        fi
      else
        grep "^$key=" "$file" | awk -F'=' '{print $2}'
      fi
    else
      cat "$file"
    fi
  else
    if [ -n "$value" ]; then
      mkdir -p "$(dirname "$file")" && touch "$file"
      echo "$key=$value" >>"$file"
    fi
  fi
}

migration() {
  local action=${1:-up}
  latest_executed=$(config migration latest_executed)

  mkdir -p /var/log/dlm/migration

  if [ "$action" = "up" ]; then
    migration_scripts=$(find "$install_dir/migration-scripts" -type f -name "*.sh" | sort)

    start_execution=false
    if [ -z "$latest_executed" ]; then
      start_execution=true
    fi

    for script in $migration_scripts; do
      script_name=$(basename "$script")

      if [ "$start_execution" = "false" ]; then
        if [ "$script_name" = "$latest_executed" ]; then
          start_execution=true
        fi
        continue
      fi

      if [ "$script_name" = "$latest_executed" ]; then
        continue
      fi

      func_cli_log "Running migration up script $script_name"
      bash "$script" up >>"/var/log/dlm/migration/${script_name}-up.log" 2>&1
      config migration latest_executed "$script_name"
    done

  elif [ "$action" = "down" ]; then
    migration_scripts=$(find "$install_dir/migration-scripts" -type f -name "*.sh" | sort | grep -A 1 "$latest_executed")

    for script in $migration_scripts; do
      script_name=$(basename "$script")
      if [ "$script_name" = "$latest_executed" ]; then continue; fi

      func_cli_log "Running migration down script $script_name"
      bash "$script" down >>"/var/log/dlm/migration/${script_name}-down.log" 2>&1

      previous_script=$(find "$install_dir/migration-scripts" -type f -name "*.sh" | sort | grep -B 1 "$script_name" | head -n 1)
      config migration latest_executed "$(basename "$previous_script")"
    done
  fi
}

start() {
  func_cli_log "Starting DLM service"
  cd "$install_dir" || exit 1
  docker compose -f docker-compose/dlm.docker-compose.yml up --force-recreate -d --wait
  cd - >/dev/null 2>&1 || true
}

stop() {
  func_cli_log "Stopping DLM service"
  cd "$install_dir" || exit 1
  docker compose -f docker-compose/dlm.docker-compose.yml stop
  cd - >/dev/null 2>&1 || true
}

logs() {
  mkdir -p /var/log/dlm/tmp/logs
  cd "$install_dir" || exit 1
  if [ -z "$1" ]; then
    services=("nginx" "api" "host-app" "emqx" "maf-zeus" "ssh-web-console" "lwm2m")
    select service in "${services[@]}"; do
      echo "You selected: $service"
      if [ -n "$service" ]; then
        docker compose -f docker-compose/dlm.docker-compose.yml logs "$service" -n 50
        break
      fi
    done
  else
    docker compose -f docker-compose/dlm.docker-compose.yml logs "$@" -n 50
  fi
  cd - >/dev/null 2>&1 || true
}

ps() {
  mkdir -p /var/log/dlm/tmp
  cd "$install_dir" || exit 1
  docker ps \
    --filter "label=product=dlm" \
    --format "table {{.Names}}\t{{.Status}}" >/dev/null 2>&1 >/var/log/dlm/tmp/ps.log
  cat /var/log/dlm/tmp/ps.log
  rm -rf /var/log/dlm/tmp/ps.log
  cd - >/dev/null 2>&1 || true
}

stats() {
  mkdir -p /var/log/dlm/tmp
  cd "$install_dir" || exit 1
  docker compose \
    -f docker-compose/dlm.docker-compose.yml stats \
    --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}" \
    --no-stream >/dev/null 2>&1 >/var/log/dlm/tmp/stats.log
  cat /var/log/dlm/tmp/stats.log
  rm -rf /var/log/dlm/tmp/stats.log
  cd - >/dev/null 2>&1 || true
}

diagnostic() {
  func_cli_log "Running diagnostic tool"
  tar_path=${1:-.}
  diag_log_dir="/var/log/dlm/diagnostic/dlm-diagnostic-$(date +%Y%m%d%H%M%S)"
  mkdir -p \
    "${diag_log_dir}/docker/logs" \
    "${diag_log_dir}/docker/inspect" \
    "${diag_log_dir}/docker/stats" \
    "${diag_log_dir}/system" \
    "${diag_log_dir}/dlm"

  services=("nginx" "api" "host-app" "emqx" "maf-zeus" "ssh-web-console" "lwm2m")

  for service in "${services[@]}"; do
    # journald container logs
    journalctl CONTAINER_TAG="dlm-${service}" --since="7 days ago" --no-pager --all >"${diag_log_dir}/docker/logs/${service}.log" 2>&1
    # docker container inspect
    docker inspect "dlm-${service}" >"${diag_log_dir}/docker/inspect/${service}.log"
  done

  # docker network inspect
  docker network inspect dlm >"${diag_log_dir}/docker/inspect/network.log"

  # get docker stats, loop 5 times and delay 1s
  stats_file="${diag_log_dir}/docker/stats/$(date +%Y%m%d%H%M%S).log"
  docker ps --format "table {{.Names}}\t{{.Status}}" >"$stats_file"
  echo "==============================" >>"$stats_file"
  for i in {1..3}; do
    docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}" >>"$stats_file"
    echo "==============================" >>"$stats_file"
    sleep 3
  done

  # get cpu / memory disk usage
  if [ -x "$(command -v top)" ]; then
    if [ "$(uname -a |grep Darwin)" ]; then
      top -l 1 >"${diag_log_dir}/system/top.log"
    else
      top -b -n 1 >"${diag_log_dir}/system/top.log"
    fi
  fi
  df -h >"${diag_log_dir}/system/df.log"
  if [ -x "$(command -v free)" ]; then
    free -h >"${diag_log_dir}/system/free.log"
  fi
  # get network information
  if [ -x "$(command -v ip)" ]; then
    ip addr show >"${diag_log_dir}/system/ip-addr-show.log"
    ip route show >"${diag_log_dir}/system/ip-route-show.log"
  fi
  # get os information
  if [ -f "/etc/os-release" ]; then
    cat /etc/os-release >"${diag_log_dir}/system/os-release.log"
  fi
  # get kenel information
  if [ -x "$(command -v uname)" ]; then
    uname -a >"${diag_log_dir}/system/uname.log"
  fi
  # get kernel modules
  if [ -x "$(command -v lsmod)" ]; then
    lsmod >"${diag_log_dir}/system/lsmod.log"
  fi
  # get system time zone information
  if [ -x "$(command -v timedatectl)" ]; then
    timedatectl >"${diag_log_dir}/system/timedatectl.log"
  fi

  # get dlm logs
  mkdir -p "${diag_log_dir}/dlm"
  log_types=(cli install)
  for log_type in "${log_types[@]}"; do
    if [ -f "/var/log/dlm/${log_type}.log" ]; then
      cp -rf "/var/log/dlm/${log_type}.log" "${diag_log_dir}/dlm/${log_type}.log"
    else
      if [ -x "$(command -v journalctl)" ]; then
        journalctl -t "dlm-${log_type}" -n 1000 >"${diag_log_dir}/dlm/${log_type}.log"
      fi
    fi
  done

  cp -rf /var/log/dlm/migration "${diag_log_dir}/dlm/migration"

  # version info
  version >"${diag_log_dir}/dlm/version.txt"

  # create tar file
  tar -czf "${tar_path}/dlm-diagnostic-$(date +%Y%m%d%H%M%S).tar.gz" -C /var/log/dlm/diagnostic .
  if [ -z "$MODE" ]; then
    rm -rf /var/log/dlm/diagnostic
  fi

  echo "Diagnostic log has been created at:"
  echo "${tar_path}/dlm-diagnostic-$(date +%Y%m%d%H%M%S).tar.gz"
  echo "Please send the diagnostic file to the Moxa Technical Support."
}

version() {
  # Check if dlm package is installed and get the installed version
  if [ -f /etc/os-release ]; then
    # shellcheck disable=SC1091
    source /etc/os-release
    if [ "$ID" = "debian" ] || [ "$ID" = "ubuntu" ]; then
      # Use dpkg to get the installed version of dlm package
      if dpkg -l dlm 2>/dev/null | grep -q "^ii"; then
        dpkg -l dlm | grep "^ii" | awk '{print $3}'
      else
        dpkg -l dlm | grep "dlm" | awk '{print $3}'
      fi
    else
      echo "development"
    fi
  else
    echo "development"
  fi
}

backup() {
  if [ "$caller" = "user" ]; then
    # check if the user wants to continue
    read -rn1 -p "DLM service will unavailable during backup. do you want to continue? [y/N] " reply
    echo
    if [[ ! $reply =~ ^[Yy]$ ]]; then
      echo "Backup canceled."
      exit 0
    fi

    # ask password for openssl encryption
    read -rsp "Enter password for backup file encryption: " password
    echo
    if [ -z "$password" ]; then
      echo "encryption password is required."
      exit 0
    fi
  fi

  dir=${1:-.}
  func_cli_log "Running backup"
  # get data & env-files directory size
  data_size=$(du -s "$USER_DATA_DIR/data" | awk '{print $1}')
  env_files_size=$(du -s "$USER_DATA_DIR/env-files" | awk '{print $1}')
  total_size=$((data_size + env_files_size))
  # ensure the backup directory is available
  if [ ! -d "$dir" ]; then
    echo "The directory $dir is not available."
    func_cli_log "Backup failed: The directory $(readlink -f "$dir") is not available."
    exit 1
  fi
  available_space=$(df "$dir" | awk 'NR==2{print $4}')
  # ensure the dir available space is enough
  if [ "$total_size" -gt "$available_space" ]; then
    echo "The available space in $dir is not enough for the backup."
    func_cli_log "Backup failed: The available space in $(readlink -f "$dir") is not enough for the backup."
    exit 1
  fi
  # create temp directory in ${dir}/${random_name}
  tmp_dir=$(mktemp -d -p "$dir")
  if [ "$caller" = "user" ]; then
    stop
  fi
  cp -rf "$USER_DATA_DIR/env-files" "$tmp_dir"
  cp -rf "$USER_DATA_DIR/data" "$tmp_dir"
  version >"$tmp_dir/dlm-version.txt"
  if [ "$caller" = "user" ]; then
    start &>/dev/null
  fi

  # Step 1: Create the initial backup tar.gz file
  backup_tgz="dlm-backup-$(date +%Y%m%d%H%M).tar.gz"
  tar -czf "$PWD/$backup_tgz" -C "$tmp_dir" .
  rm -rf "$tmp_dir"

  if [ "$caller" = "user" ]; then
    # Step 2: Calculate SHA256 hash and save to sha256.txt
    sha256sum "$PWD/$backup_tgz" | awk '{print $1}' > "$PWD/sha256.txt"

    # Step 3: Package both files together into a new tar.gz
    package_tgz="dlm-backup-package-$(date +%Y%m%d%H%M).tar.gz"
    tar -czf "$PWD/$package_tgz" -C "$PWD" "$backup_tgz" "sha256.txt"

    # Step 4: Encrypt the package using openssl in docker
    final_file="${dir}/dlm-backup-$(date +%Y%m%d%H%M).tgz.enc"
    echo "Encrypting backup package..."
    if func_docker_openssl enc -aes-256-cbc -salt -in "$PWD/$package_tgz" -out "$final_file" -k "$password" -pbkdf2; then
      echo "Encryption successful."
      # Clean up temporary files
      rm -f "$PWD/$backup_tgz" "$PWD/sha256.txt" "$PWD/$package_tgz"

      echo "Backup file has been created at:"
      echo "$final_file"
      func_cli_log "Backup file has been created at: $(readlink -f "$final_file")"
    else
      echo "Encryption failed. Please check Docker and openssl container."
      func_cli_log "Backup failed: Encryption failed."
      # Clean up temporary files on failure
      rm -f "$PWD/$backup_tgz" "$PWD/sha256.txt" "$PWD/$package_tgz"
      exit 1
    fi
  fi
}

func_verify_backup() {
  file=${1:-}
  if [ -z "$file" ]; then
    echo "Please provide the backup file path."
    exit 1
  fi
  if [ ! -f "$file" ]; then
    echo "Backup file not found."
    exit 1
  fi

  # check file can be decrypted
  read -rsp "Enter password for backup file decryption: " password
  echo
  if [ -z "$password" ]; then
    echo "decryption password is required."
    exit 0
  fi

  # Create temp directory for verification
  tmp_dir=$(mktemp -d -p "$PWD")

  # Step 1: Decrypt the backup file
  decrypted_package="$tmp_dir/decrypted_package.tar.gz"
  func_docker_openssl enc -d -aes-256-cbc -in "$file" -out "$decrypted_package" -k "$password" -pbkdf2
  if ! func_docker_openssl enc -d -aes-256-cbc -in "$file" -out "$decrypted_package" -k "$password" -pbkdf2; then
    echo "Decryption failed. Please check the decryption password."
    rm -rf "$tmp_dir"
    exit 1
  fi

  # Step 2: Extract the package to get tar.gz and sha256.txt
  tar -xzf "$decrypted_package" -C "$tmp_dir"

  # Step 3: Find the backup tar.gz file and sha256.txt
  backup_file=$(find "$tmp_dir" -name "dlm-backup-*.tar.gz" | head -1)
  sha256_file="$tmp_dir/sha256.txt"

  if [ ! -f "$backup_file" ] || [ ! -f "$sha256_file" ]; then
    echo "Backup file is invalid: missing backup data or SHA256 file."
    rm -rf "$tmp_dir"
    exit 1
  fi

  # Step 4: Verify SHA256 hash
  expected_hash=$(cat "$sha256_file")
  actual_hash=$(sha256sum "$backup_file" | awk '{print $1}')

  if [ "$expected_hash" != "$actual_hash" ]; then
    echo "Backup file is invalid: SHA256 hash mismatch."
    rm -rf "$tmp_dir"
    exit 1
  else
    echo "Backup file is valid."
  fi

  # Clean up
  rm -rf "$tmp_dir"
}

restore() {
  if [ "$caller" = "user" ]; then
    read -rn1 -p "DLM service will unavailable during restore. do you want to continue? [y/N] " reply
    echo
    if [[ ! $reply =~ ^[Yy]$ ]]; then
      echo "Restore canceled."
      exit 0
    fi
  fi

  file=${1:-}
  func_cli_log "Running restore"
  if [ -z "$file" ]; then
    echo "Please provide the backup file path."
    func_cli_log "Restore failed: Please provide the backup file path."
    exit 1
  fi
  if [ ! -f "$file" ]; then
    echo "Backup file not found."
    func_cli_log "Restore failed: Backup file not found."
    exit 1
  fi

  if [ "$caller" = "user" ]; then
    func_verify_backup "$file"
    stop >/dev/null 2>&1 || true
  fi

  tmp_dir=$(mktemp -d -p "$PWD")

  # Decrypt the backup file
  if echo "$file" | grep -q ".enc$"; then
    read -rsp "Enter password for backup file decryption: " password
    echo
    if [ -z "$password" ]; then
      echo "decryption password is required."
      exit 0
    fi

    # Step 1: Decrypt the package
    decrypted_package="$tmp_dir/decrypted_package.tar.gz"
    if ! func_docker_openssl enc -d -aes-256-cbc -in "$file" -out "$decrypted_package" -k "$password" -pbkdf2; then
      echo "Decryption failed. Please check the decryption password."
      func_cli_log "Restore failed: Decryption failed. Please check the decryption password."
      rm -rf "$tmp_dir"
      exit 1
    fi

    # Step 2: Extract the package to get tar.gz and sha256.txt
    tar -xzf "$decrypted_package" -C "$tmp_dir"

    # Step 3: Find the actual backup tar.gz file
    backup_file=$(find "$tmp_dir" -name "dlm-backup-*.tar.gz" | head -1)
    sha256_file="$tmp_dir/sha256.txt"

    if [ ! -f "$backup_file" ] || [ ! -f "$sha256_file" ]; then
      echo "Restore failed: Invalid backup format."
      func_cli_log "Restore failed: Invalid backup format."
      rm -rf "$tmp_dir"
      exit 1
    fi

    # Step 4: Verify SHA256 hash
    expected_hash=$(cat "$sha256_file")
    actual_hash=$(sha256sum "$backup_file" | awk '{print $1}')

    if [ "$expected_hash" != "$actual_hash" ]; then
      echo "Restore failed: Backup integrity check failed."
      func_cli_log "Restore failed: Backup integrity check failed."
      rm -rf "$tmp_dir"
      exit 1
    fi

    # Step 5: Extract the actual backup data
    restore_tmp_dir=$(mktemp -d -p "$PWD")
    tar -xzf "$backup_file" -C "$restore_tmp_dir"
    file="$backup_file"
  else
    # Handle unencrypted backup (legacy support)
    restore_tmp_dir="$tmp_dir"
    tar -xzf "$file" -C "$restore_tmp_dir"
  fi

  # check if version is compatible
  if [ -f "$restore_tmp_dir/dlm-version.txt" ]; then
    backup_version=$(cat "$restore_tmp_dir/dlm-version.txt")
    current_version=$(version)
    if [ "$backup_version" != "$current_version" ]; then
      func_cli_log "The backup version is $backup_version, current version is $current_version."
      # ask user to continue
      read -rn1 -p "The backup version is $backup_version, current version is $current_version. Do you want to continue? [y/N] " reply
      echo
      if [[ ! $reply =~ ^[Yy]$ ]]; then
        echo "Restore canceled."
        func_cli_log "Restore canceled."
        rm -rf "$tmp_dir"
        if [ "$restore_tmp_dir" != "$tmp_dir" ]; then
          rm -rf "$restore_tmp_dir"
        fi
        exit 0
      fi
    fi
  fi

  cp -rf "$restore_tmp_dir/env-files" "$USER_DATA_DIR"
  cp -rf "$restore_tmp_dir/data" "$USER_DATA_DIR"

  # Remove emqx data to prevent emqx startup failures
  rm -rf /var/lib/dlm/data/emqx/data/*

  # Clean up temporary directories
  rm -rf "$tmp_dir"
  if [ "$restore_tmp_dir" != "$tmp_dir" ]; then
    rm -rf "$restore_tmp_dir"
  fi

  func_cli_log "Restore completed"
  func_cli_log "Running migration"
  migration up
  func_cli_log "Migration done"
  if [ "$caller" = "user" ]; then
    source_environment_variables
    start
  fi
}

remove_not_used_docker_images() {
  images=$(docker images --format "{{.Repository}}:{{.Tag}}" -f "label=product=dlm")
  running_images=$(docker ps --format "{{.Image}}" -f "label=product=dlm" | sort | uniq)
  for image in $images; do
    if ! echo "$running_images" | grep -q "$image"; then
      if ! echo "$image" | grep -q "openssl"; then
        docker rmi -f "$image" >/dev/null 2>&1 || true
      fi
    fi
  done
  docker rmi "$(docker images -f "dangling=true" -q)" >/dev/null 2>&1 || true
}

change_domain() {
  domain=${1:-}
  if [ -z "$domain" ]; then
    echo "Please provide the domain name."
    exit 1
  fi
  previous_domain=$(config global SERVICE_FQDN)
  if [ "$domain" = "$previous_domain" ]; then
    echo "The domain name is the same as the previous one."
    exit 0
  fi
  # verify is the domain is valid
  if ! echo "$domain" | grep -qE "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"; then
    echo "Invalid domain name."
    exit 1
  fi
  # confirm the domain change
  echo
  echo "previous domain: $previous_domain"
  echo "new domain: $domain"
  echo
  read -rn1 -p "Changing the domain name will restart the DLM service. Do you want to continue? [y/N] " reply
  if [[ ! $reply =~ ^[Yy]$ ]]; then
    echo "Domain name change canceled."
    exit 0
  fi
  func_cli_log "Changing domain name to $domain"
  config global SERVICE_FQDN "$domain"
  rm -rf "$USER_DATA_DIR/data/certs/web/*"
  rm -rf "$USER_DATA_DIR/env-files/certs/web.env"
  /opt/moxa/dlm/migration-scripts/000-create-certs.sh up
  stop
  start
  func_cli_log "Domain name changed to $domain"
}

clean() {
  rm -rf /var/log/dlm/diagnostic
  rm -rf /var/log/dlm/tmp
  rm -rf /usr/local/moxa-dlm-apt-repo
  rm -rf /usr/local/moxa-dlm-offline-apt-repo
  rm -rf /etc/apt/sources.list.d/moxa-dlm.list
  remove_not_used_docker_images
}

main() {
  arg1=${1:-usage}
  case $arg1 in
  start | up | u | s | restart | r)
    start
    ;;
  stop | down | halt | shutdown)
    stop
    ;;
  logs | log | l)
    logs ${*:2}
    ;;
  status | ps | ls)
    ps
    ;;
  stats | st)
    stats
    ;;
  diagnostic | diag)
    diagnostic ${*:2}
    ;;
  version | v)
    version
    ;;
  config | c)
    config ${*:2}
    ;;
  migration | mg)
    migration ${*:2}
    ;;
  backup | b)
    backup ${*:2}
    ;;
  restore | rs)
    restore ${*:2}
    ;;
  verify-backup | vb)
    func_verify_backup ${*:2}
    ;;
  clean | clear)
    clean
    ;;
  change-domain | cd)
    change_domain ${*:2}
    ;;
  certbot | cb)
    certbot_obtain_certificate
    ;;
  *)
    usage
    ;;
  esac
}

source_environment_variables
main "$@"
