#!/bin/bash
#
# ***** BEGIN LICENSE BLOCK *****
# Zimbra Collaboration Suite, Network Edition.
# Copyright (C) 2025 Synacor, Inc.  All Rights Reserved.
# ***** END LICENSE BLOCK *****
#
usage() {
  echo "Usage: $0 [options]" >&3
  echo "" >&3
  echo "Options:" >&3
  echo "  -h, --help             Show this help message" >&3
  echo "  --filename=<file> <optional arg>     Specify input file containing domain list" >&3
  echo "  --parallelismFactor=<number> <optional arg>   Number of parallel domain provisioning processes (default: 1)" >&3
  echo ""
  echo "Description:" >&3
  echo "  This script provisions chat accounts for domains with the chat feature enabled." >&3
  echo "  If --filename option is provided, it provisions chat accounts for domains listed in the file (one domain per line) that have the chat feature enabled." >&3
  echo "  If no --filename option is provided, it provisions chat accounts for all domains that have the chat feature enabled." >&3
  echo "  The parallelismFactor should be set to no more than 50% of the available CPU cores" >&3
  echo "  on the most constrained component — either the Chat server or the Chat PostgreSQL server." >&3
  echo "  For example, if the Chat server has 6 cores and the Chat PostgreSQL server has 4 cores," >&3
  echo "  use the lower core count (i.e., 4) as the reference and set parallelismFactor to 2 (50% of 4)." >&3
  echo "" >&3
  echo "Note:" >&3
  echo "  Domains without the chat feature enabled will be skipped." >&3
  echo "  It is recommended to run this script in the background to avoid session timeouts." >&3
  echo "" >&3
  echo "Example:" >&3
  echo "  $0 --filename=/path/to/chatdomains.txt --parallelismFactor=2 & disown" >&3
  echo "  $0 & disown" >&3
}

exec 3>&1 4>&2

# User check
if [  x$(whoami) != xzimbra ]; then
  echo "Please run command as zimbra user..." >&3
  exit 1
fi

# Zimbra version check
zimbra_version=$( /opt/zimbra/bin/zmcontrol -v | cut -d' ' -f2)
if [ $(printf "%s\n" "10.1.0" "$zimbra_version" | sort -V | head -n1) != "10.1.0" ]; then
  echo "Zimbra version $zimbra_version is not supported." >&3
  exit 1
fi

# Check if /opt/zimbra/chat exists and is writable
if [ ! -d "/opt/zimbra/chat" ] || [ ! -w "/opt/zimbra/chat" ]; then
  echo -e "\033[33m************************************************************\033[0m" >&3
  echo >&3
  echo -e "\033[31m  chat directory not found or not writable.\033[0m" >&3
  echo "  Please run the following commands as root:" >&3
  echo "  mkdir -p /opt/zimbra/chat" >&3
  echo "  chown zimbra:zimbra /opt/zimbra/chat" >&3
  echo >&3
  echo "Then re-run this script." >&3
  echo -e "\033[33m************************************************************\033[0m" >&3
  exit 1
fi

file=""
parallel_count=1
# Argument parsing: only allow --filename=filename or no args
if [ "$#" -gt 0 ]; then
  for arg in "$@"; do
    case $arg in
      --filename=*)
        file="${arg#*=}"
        if [ -z "$file" ]; then
          echo "Error: --filename provided but no filename specified." >&3
          echo "Usage: $0 --filename=<filename.txt>" >&3
          exit 1
        fi
        ;;
      --parallelismFactor=*)
        parallel_count="${arg#*=}"
        if ! [[ "$parallel_count" =~ ^[0-9]+$ ]] || [ "$parallel_count" -lt 1 ]; then
          echo "Error: --parallel must be a positive integer." >&3
          exit 1
        fi
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        echo "Error: Invalid argument '$arg'." >&3
        exit 1
        ;;
    esac
  done
fi

HOSTNAME=localhost
PORT=$(/opt/zimbra/bin/zmlocalconfig -s zimbra_admin_service_port | awk '{print $3}')
URL="https://${HOSTNAME}:${PORT}/service/admin/soap"

get_auth_token() {
  USERNAME=$(/opt/zimbra/bin/zmlocalconfig -s zimbra_ldap_user | awk '{print $3}')
  PASSWORD=$(/opt/zimbra/bin/zmlocalconfig -s zimbra_ldap_password | awk '{print $3}')
  auth_response=$(curl -s -k -X POST \
    "${URL}" \
    -H "Content-Type: application/soap+xml; charset=utf-8" \
    -d "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">
  <soap:Body>
    <AuthRequest xmlns=\"urn:zimbraAdmin\">
      <name>${USERNAME}</name>
      <password>${PASSWORD}</password>
    </AuthRequest>
  </soap:Body>
</soap:Envelope>")

  AUTH_TOKEN=$(echo "${auth_response}" | grep -oP '(?<=<authToken>).*?(?=</authToken>)')

  if [[ -z "$AUTH_TOKEN" ]]; then
    echo "Failed to retrieve auth token. Check credentials or server access." >&3
    exit 1
  fi
}

get_auth_token
AUTH_TOKEN_VALUE="$AUTH_TOKEN"
ADMIN_EMAIL=$(/opt/zimbra/bin/zmprov gcf zimbraVersionCheckNotificationEmail | awk '{print $2}')
TMPDIR="/tmp/chat_status_$$"
mkdir -p "$TMPDIR"
export TMPDIR

prov_chat_domains() {
  local domain_name="$1"
  local domain_id="$2"
  local domain_log="$TMPDIR/${domain_name}.log"
  local domain_status="$TMPDIR/${domain_name}.status"

  {
    # Skip invalid entries
    if [[ -z "$domain_name" || -z "$domain_id" ]]; then
      echo "Skipping invalid or blank domain line: $domain_name, $domain_id"
      return
    fi

  echo -e "Provisioning chat accounts for domain:\n$domain_name"

  # SOAP Request
  local SOAP_REQUEST="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">
  <soap:Header>
    <context xmlns=\"urn:zimbra\">
      <authToken>${AUTH_TOKEN_VALUE}</authToken>
    </context>
  </soap:Header>
  <soap:Body>
    <ProvChatAccountsRequest xmlns=\"urn:zimbraAdmin\">
      <domain by=\"id\">${domain_id}</domain>
    </ProvChatAccountsRequest>
  </soap:Body>
</soap:Envelope>"

    response=$(curl -s -k -X POST \
      "$URL" \
      -H "Content-Type: application/soap+xml; charset=utf-8" \
      -d "$SOAP_REQUEST" -w "%{http_code}")
    http_status_code=${response: -3}
    response=${response::-3}

    if [ "$http_status_code" == "000" ]; then
      echo "Mailbox is unresponsive please contact administrator or contact support and rerun later."
      echo "FAILED" > "$domain_status"
    elif [ "$http_status_code" != "200" ]; then
      echo "Error provisioning chat accounts"
      echo "Error Code: $http_status_code"
      echo "FAILED" > "$domain_status"
    else
      success_count=$(echo "$response" | grep -oP '(?<=<numSucceeded>).*?(?=</numSucceeded>)')
      failure_count=$(echo "$response" | grep -oP '(?<=<numFailed>).*?(?=</numFailed>)')

      echo "Accounts provisioned: $success_count"
      echo "Accounts not provisioned: $failure_count"

      if [ "$failure_count" -gt 0 ]; then
        echo "FAILED" > "$domain_status"
      elif [ "$success_count" -gt 0 ]; then
        echo "SUCCESS" > "$domain_status"
      else
        echo "SKIPPED" > "$domain_status"
      fi
    fi
    echo "--------------------------------------------------"
  } > "$domain_log" 2>&1
}

send_email_via_soap() {
  local subject="$1"
  local body_file="$2"
  local recipient="$3"

  local body_content
  body_content=$(<"$body_file")

  local soap_request="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">
  <soap:Header>
    <context xmlns=\"urn:zimbra\">
      <authToken>${AUTH_TOKEN_VALUE}</authToken>
      <account by=\"name\">${recipient}</account>
    </context>
  </soap:Header>
  <soap:Body>
    <AddMsgRequest xmlns=\"urn:zimbraMail\">
      <m l=\"2\" f=\"su\">
        <content>Subject: ${subject}
To: ${recipient}
From: ${recipient}
Content-Type: text/plain; charset=utf-8

${body_content}</content>
      </m>
    </AddMsgRequest>
  </soap:Body>
</soap:Envelope>"

  local response
  response=$(curl -s -k -X POST "$URL" \
    -H "Content-Type: application/soap+xml; charset=utf-8" \
    -d "$soap_request")

  if [[ "$response" != *"<AddMsgResponse"* ]]; then
    echo "Failed to add mail to ${recipient}'s mailbox" >&2
  fi
}


send_email_summary() {
  local subject="Zimbra Chat Provisioning Summary - $(date '+%Y-%m-%d %H:%M')"
  local body_file="/tmp/chat_provision_summary_$$.txt"
  local result_file="/opt/zimbra/chat/provisionedChatDomainAccountsResult.txt"

  {
    echo "Zimbra Chat Provisioning Summary"
    echo "--------------------------------"
    echo "Date: $(date)"
    echo ""
    echo "Successful Domains     : $successful_domains"
    echo "Domains with Errors    : $failed_domains"
    echo ""
    echo "Detailed log file: $result_file"
  } > "$body_file"

  send_email_via_soap "$subject" "$body_file" "$ADMIN_EMAIL"

  rm -f "$body_file"
}

send_running_status_email() {
  local start_time=$(date '+%Y-%m-%d %H:%M:%S')
  local subject="Zimbra Chat Provisioning is still running..."
  local body_file="/tmp/chat_provisioning_status_$$.txt"

  while true; do
    sleep 3600  # wait 1 hr

    local success=0
    local failed=0
    local skipped=0

    for status_file in "$TMPDIR"/*.status; do
      [ ! -f "$status_file" ] && continue
      local status=$(cat "$status_file")
      case "$status" in
        SUCCESS) ((success++)) ;;
        FAILED)  ((failed++)) ;;
        SKIPPED) ((skipped++)) ;;
      esac
    done

    {
      echo "Zimbra Chat Provisioning Status Update"
      echo "--------------------------------------"
      echo "Start time       : $start_time"
      echo "Current time     : $(date)"
      echo "Successful       : $success"
      echo "Failures         : $failed"
    } > "$body_file"

    send_email_via_soap "$subject" "$body_file" "$ADMIN_EMAIL"
    rm -f "$body_file"
  done
}

get_domain_details() {
  local domain_name="$1"


  local soap_request="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
  <soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">
    <soap:Header>
      <context xmlns=\"urn:zimbra\">
        <authToken>${AUTH_TOKEN_VALUE}</authToken>
      </context>
    </soap:Header>
    <soap:Body>
      <GetDomainRequest xmlns=\"urn:zimbraAdmin\" applyConfig=\"false\">
        <domain by=\"name\">${domain_name}</domain>
      </GetDomainRequest>
    </soap:Body>
  </soap:Envelope>"

  local response
  response=$(curl -s -k -X POST \
    "$URL" \
    -H "Content-Type: application/soap+xml" \
    -d "${soap_request}")

  local zimbraId
  local zulipEnabled

  zimbraId=$(echo "$response" | grep -oP '(?<=<a n="zimbraId">)[^<]+')
  zulipEnabled=$(echo "$response" | grep -oP '(?<=<a n="zimbraFeatureZulipChatEnabled">)[^<]+')

  if [[ -z "$response" || -z "$zimbraId" || -z "$zulipEnabled" ]]; then
    return 1
  fi

  echo "$zimbraId,$zulipEnabled"
}

provision_all_domains() {
  echo -n "Provisioning all domains ..." >&3
  local feature="zimbraFeatureZulipChatEnabled"
  local attrs="zimbraFeatureZulipChatEnabled,zimbraId"
  domains=$(/opt/zimbra/bin/zmjava com.zimbra.util.DomainFilterBasedOnFeature "$feature" "$attrs")

  echo "$domains" | while IFS= read -r line; do
    domain_name=$(echo "$line" | cut -d',' -f1 | cut -d':' -f2- | xargs)
    domain_details=$(get_domain_details "$domain_name") || continue
    domain_id=$(echo "$domain_details" | cut -d',' -f1)
    echo "$domain_name,$domain_id"
  done | xargs -P "$parallel_count" -n 1 -I {} bash -c 'prov_chat_domains $(echo {} | cut -d"," -f1) $(echo {} | cut -d"," -f2)'
  [ -n "${EMAIL_LOOP_PID:-}" ] && kill "$EMAIL_LOOP_PID" 2>/dev/null
}


export -f prov_chat_domains get_domain_details
export AUTH_TOKEN_VALUE URL TMPDIR

process_domains() {
  local file="$1"

  local file_name
  file_name=$(basename "$file")
  local name_without_ext
  name_without_ext="${file_name%.txt}"

  if [[ "$file_name" != *.txt ]] || [[ -z "$name_without_ext" || "$name_without_ext" == "." ]]; then
    echo "Invalid filename: '$file'" >&2
    echo "Please provide a valid filename ending with .txt" >&2
    exit 1
  fi

  if [[ ! -f "$file" ]]; then
    echo "File '$file' does not exist or is not a regular file" >&2
    exit 1
  fi
  echo "Processing domains from file: $file" >&3
  cat "$file" | while read -r domain_name; do
    domain_name=$(echo "$domain_name" | xargs)
    domain_info=$(get_domain_details "$domain_name") || continue
    domain_id=$(echo "$domain_info" | cut -d',' -f1)
    enabled=$(echo "$domain_info" | cut -d',' -f2)
    [ "$enabled" == "TRUE" ] && echo "$domain_name,$domain_id"
  done | xargs -P "$parallel_count" -n 1 -I{} bash -c 'prov_chat_domains $(echo {} | cut -d"," -f1) $(echo {} | cut -d"," -f2)'
  [ -n "${EMAIL_LOOP_PID:-}" ] && kill "$EMAIL_LOOP_PID" 2>/dev/null
}

print_summary_and_merge_logs() {
  result_file="/opt/zimbra/chat/provisionedChatDomainAccountsResult.txt"
  > "$result_file"
  success=0
  failed=0
  skipped=0
  failed_domains_list=""

  for status_file in "$TMPDIR"/*.status; do
    [ ! -f "$status_file" ] && continue
    domain_name=$(basename "$status_file" .status)
    status=$(cat "$status_file")
    case "$status" in
      SUCCESS)
        ((success++))
        cat "$TMPDIR/$domain_name.log" >> "$result_file"
        ;;
      FAILED)
        ((failed++))
        failed_domains_list+="$domain_name"$'\n'
        cat "$TMPDIR/$domain_name.log" >> "$result_file"
        ;;
      SKIPPED)
        ((skipped++))
        ;;
    esac
  done

  echo -e "\n\033[32mDomains with successful provisioning: $success\033[0m" >&3
  echo -e "\033[31mDomains with provisioning errors: $failed\033[0m" >&3
  echo "Check /opt/zimbra/chat/provisionedChatDomainAccountsResult.txt for details." >&3

  if [[ -n "$failed_domains_list" ]]; then
    {
      echo -e "\nDomain(s) with failure -"
      echo "$failed_domains_list"
    } >> "$result_file"
  fi

  export successful_domains=$success
  export failed_domains=$failed
}

cleanup() {
  [ -n "${EMAIL_LOOP_PID:-}" ] && kill "$EMAIL_LOOP_PID" 2>/dev/null
  rm -rf "$TMPDIR"
}
trap cleanup EXIT

send_running_status_email &
EMAIL_LOOP_PID=$!

if [ -z "$file" ]; then
  provision_all_domains
  print_summary_and_merge_logs
  send_email_summary
else
  process_domains "$file"
  print_summary_and_merge_logs
  send_email_summary
fi
