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

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
project_dir="${MAILHUB_CERT_PROJECT_DIR:-$(cd "${script_dir}/.." && pwd -P)}"
project_dir="$(cd "${project_dir}" && pwd -P)"
env_file="${MAILHUB_CERT_ENV_FILE:-${project_dir}/.env}"
source_dir="${MAILHUB_CERT_SOURCE_DIR:-}"
restart_app="${MAILHUB_CERT_RESTART:-0}"
verify_host="${MAILHUB_CERT_VERIFY_HOST:-}"
verify_endpoints="${MAILHUB_CERT_VERIFY_ENDPOINTS:-465 993}"
certs_dir="${project_dir}/certs"
lock_file="${MAILHUB_CERT_LOCK_FILE:-${certs_dir}/.sync-tls-certificate.lock}"

work_dir=""
target_cert=""
target_key=""
backup_cert_exists=0
backup_key_exists=0
rollback_required=0
target_owner_uid=""
target_reader_gid=""

fail() {
  echo "Certificate sync failed: $*" >&2
  exit 1
}

read_env_value() {
  local key="$1"
  local line value
  line="$(grep -E "^${key}=" "${env_file}" 2>/dev/null | tail -n 1 || true)"
  value="${line#*=}"
  value="${value%$'\r'}"
  if [[ "${value}" == \"*\" && "${value}" == *\" ]]; then
    value="${value:1:${#value}-2}"
  elif [[ "${value}" == \'*\' && "${value}" == *\' ]]; then
    value="${value:1:${#value}-2}"
  fi
  printf '%s' "${value}"
}

resolve_target() {
  local container_path="$1"
  local label="$2"
  local relative
  [[ "${container_path}" == /certs/* ]] || fail "${label} must point to a direct file under /certs."
  relative="${container_path#/certs/}"
  [[ -n "${relative}" && "${relative}" != */* && "${relative}" != "." && "${relative}" != ".." ]] \
    || fail "${label} must point to a direct file under /certs."
  printf '%s/%s' "${certs_dir}" "${relative}"
}

validate_pair() {
  local certificate="$1"
  local private_key="$2"
  local label="$3"
  local cert_public_key="${work_dir}/${label}-cert-public.der"
  local key_public_key="${work_dir}/${label}-key-public.der"

  openssl x509 -in "${certificate}" -noout >/dev/null 2>&1 \
    || fail "${label} certificate is invalid."
  openssl x509 -in "${certificate}" -checkend 86400 -noout >/dev/null 2>&1 \
    || fail "${label} certificate expires within 24 hours."
  openssl x509 -in "${certificate}" -checkhost "${mail_hostname}" -noout >/dev/null 2>&1 \
    || fail "${label} certificate does not cover ${mail_hostname}."
  openssl pkey -in "${private_key}" -check -noout >/dev/null 2>&1 \
    || fail "${label} private key is invalid."
  openssl x509 -in "${certificate}" -pubkey -noout \
    | openssl pkey -pubin -outform DER >"${cert_public_key}" 2>/dev/null
  openssl pkey -in "${private_key}" -pubout -outform DER >"${key_public_key}" 2>/dev/null
  cmp -s "${cert_public_key}" "${key_public_key}" \
    || fail "${label} certificate and private key do not match."
}

resolve_target_ownership() {
  local detected_gid=""
  if [[ "$(id -u)" == "0" ]]; then
    target_owner_uid="${MAILHUB_CERT_OWNER_UID:-0}"
    if [[ -n "${MAILHUB_CERT_READER_GID:-}" ]]; then
      target_reader_gid="${MAILHUB_CERT_READER_GID}"
    else
      command -v docker >/dev/null 2>&1 \
        || fail "docker is required to detect the MailHub app group id."
      detected_gid="$(cd "${project_dir}" && docker compose exec -T app node -e \
        'process.stdout.write(String(process.getgid()))' 2>/dev/null || true)"
      [[ -n "${detected_gid}" ]] \
        || fail "unable to determine the MailHub app group id; set MAILHUB_CERT_READER_GID explicitly."
      target_reader_gid="${detected_gid}"
    fi
    [[ "${target_owner_uid}" =~ ^[0-9]+$ ]] || fail "MAILHUB_CERT_OWNER_UID must be numeric."
    [[ "${target_reader_gid}" =~ ^[0-9]+$ ]] || fail "MAILHUB_CERT_READER_GID must be numeric."
  elif [[ -n "${MAILHUB_CERT_OWNER_UID:-}" || -n "${MAILHUB_CERT_READER_GID:-}" ]]; then
    fail "certificate ownership can only be changed when the sync runs as root."
  fi
}

set_file_metadata() {
  local file="$1"
  local mode="$2"
  chmod "${mode}" "${file}"
  if [[ -n "${target_owner_uid}" ]]; then
    chown "${target_owner_uid}:${target_reader_gid}" "${file}"
  fi
}

set_target_metadata() {
  set_file_metadata "$1" 0644
  set_file_metadata "$2" 0640
}

wait_for_app_health() {
  local attempt
  for attempt in $(seq 1 45); do
    if docker compose exec -T app node -e \
      "fetch('http://127.0.0.1:3000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" \
      >/dev/null 2>&1; then
      return 0
    fi
    sleep 2
  done
  return 1
}

wait_for_dovecot_health() {
  local attempt
  for attempt in $(seq 1 45); do
    if docker compose exec -T dovecot doveconf -n >/dev/null 2>&1; then
      return 0
    fi
    sleep 2
  done
  return 1
}

restart_tls_services() {
  docker compose restart app dovecot
  wait_for_app_health || fail "MailHub app did not become healthy after the certificate restart."
  wait_for_dovecot_health || fail "Dovecot did not become healthy after the certificate restart."
}

verify_container_access() {
  docker compose exec -T app node -e \
    "const fs=require('node:fs');const tls=require('node:tls');tls.createSecureContext({cert:fs.readFileSync(process.argv[1]),key:fs.readFileSync(process.argv[2])});" \
    "${container_cert}" "${container_key}" >/dev/null 2>&1 \
    || fail "MailHub app cannot read or parse the synchronized certificate pair."
  docker compose exec -T dovecot sh -c \
    'test -r "$1" && test -r "$2"' -- "${container_cert}" "${container_key}" >/dev/null 2>&1 \
    || fail "Dovecot cannot read the synchronized certificate pair."
}

tls_endpoint_matches() {
  local port="$1" service="app" container_port="$1"
  local mapping container_id mapped_host mapped_port connect_host connection output presented
  local expected_fingerprint actual_fingerprint
  local -a protocol_args=()

  [[ "${port}" =~ ^[0-9]+$ ]] || return 1
  case "${port}" in
    25|587|2525) protocol_args=(-starttls smtp) ;;
    110) service="dovecot"; container_port="31110"; protocol_args=(-starttls pop3) ;;
    143) service="dovecot"; container_port="31143"; protocol_args=(-starttls imap) ;;
    993) service="dovecot"; container_port="31993" ;;
    995) service="dovecot"; container_port="31995" ;;
  esac
  if ! mapping="$(docker compose port "${service}" "${container_port}" 2>/dev/null | head -n 1)" \
    || [[ -z "${mapping}" ]]; then
    container_id="$(docker compose ps -q "${service}" 2>/dev/null | head -n 1 || true)"
    if [[ -n "${container_id}" ]]; then
      mapping="$(docker port "${container_id}" "${container_port}/tcp" 2>/dev/null | head -n 1 || true)"
    else
      mapping=""
    fi
  fi
  [[ -n "${mapping}" ]] || return 1
  mapped_port="${mapping##*:}"
  if [[ "${mapping}" == \[*\]:* ]]; then
    mapped_host="${mapping#\[}"
    mapped_host="${mapped_host%%\]*}"
  else
    mapped_host="${mapping%:*}"
  fi
  connect_host="${verify_host:-${mapped_host}}"
  [[ "${connect_host}" != "0.0.0.0" ]] || connect_host="127.0.0.1"
  [[ "${connect_host}" != "::" ]] || connect_host="::1"
  if [[ "${connect_host}" == *:* && "${connect_host}" != \[*\] ]]; then
    connection="[${connect_host}]:${mapped_port}"
  else
    connection="${connect_host}:${mapped_port}"
  fi
  output="${work_dir}/tls-${port}.txt"
  presented="${work_dir}/tls-${port}.pem"

  if command -v timeout >/dev/null 2>&1; then
    if ! timeout 15 openssl s_client "${protocol_args[@]}" -connect "${connection}" -servername "${mail_hostname}" -showcerts \
      -verify_hostname "${mail_hostname}" -verify_return_error \
      < /dev/null >"${output}" 2>/dev/null; then return 1; fi
  elif ! openssl s_client "${protocol_args[@]}" -connect "${connection}" -servername "${mail_hostname}" -showcerts \
    -verify_hostname "${mail_hostname}" -verify_return_error \
    < /dev/null >"${output}" 2>/dev/null; then
    return 1
  fi
  openssl x509 -in "${output}" -out "${presented}" >/dev/null 2>&1 \
    || return 1
  openssl x509 -in "${presented}" -checkhost "${mail_hostname}" -noout >/dev/null 2>&1 \
    || return 1
  expected_fingerprint="$(openssl x509 -in "${target_cert}" -noout -fingerprint -sha256)"
  actual_fingerprint="$(openssl x509 -in "${presented}" -noout -fingerprint -sha256)"
  [[ -n "${expected_fingerprint}" && "${expected_fingerprint}" == "${actual_fingerprint}" ]] \
    || return 1
}

verify_tls_endpoint() {
  tls_endpoint_matches "$1" \
    || fail "MailHub TLS endpoint $1 is not serving a valid synchronized certificate."
}

restore_previous_pair() {
  local rollback_failed=0
  set +e
  if [[ "${backup_cert_exists}" == "1" ]]; then
    cp -a "${work_dir}/backup-cert.pem" "${target_cert}"
  else
    rm -f -- "${target_cert}"
  fi
  [[ "$?" == "0" ]] || rollback_failed=1
  if [[ "${backup_key_exists}" == "1" ]]; then
    cp -a "${work_dir}/backup-key.pem" "${target_key}"
  else
    rm -f -- "${target_key}"
  fi
  [[ "$?" == "0" ]] || rollback_failed=1
  set -e
  return "${rollback_failed}"
}

on_exit() {
  local status=$?
  trap - EXIT
  if [[ "${status}" != "0" && "${rollback_required}" == "1" ]]; then
    echo "Certificate sync failed after promotion; restoring the previous certificate pair." >&2
    if restore_previous_pair; then
      if [[ "${restart_app}" == "1" ]]; then
        set +e
        docker compose restart app dovecot >/dev/null 2>&1
        wait_for_app_health >/dev/null 2>&1
        wait_for_dovecot_health >/dev/null 2>&1
        set -e
      fi
      echo "Previous MailHub certificate pair restored." >&2
    else
      echo "Certificate rollback failed; inspect ${target_cert} and ${target_key} immediately." >&2
    fi
  fi
  if [[ -n "${work_dir}" && -d "${work_dir}" ]]; then
    rm -rf -- "${work_dir}"
  fi
  exit "${status}"
}
trap on_exit EXIT

[[ -f "${env_file}" ]] || fail "environment file not found: ${env_file}"
[[ "${restart_app}" == "0" || "${restart_app}" == "1" ]] \
  || fail "MAILHUB_CERT_RESTART must be 0 or 1."
command -v openssl >/dev/null 2>&1 || fail "openssl is required."

[[ ! -L "${certs_dir}" ]] || fail "certificate directory must not be a symbolic link: ${certs_dir}"
mkdir -p "${certs_dir}"
canonical_certs_dir="$(cd "${certs_dir}" && pwd -P)"
[[ "${canonical_certs_dir}" == "${certs_dir}" ]] \
  || fail "certificate directory resolves outside the project: ${certs_dir}"

if command -v flock >/dev/null 2>&1; then
  if [[ -L "${lock_file}" ]]; then
    fail "certificate lock file must not be a symbolic link: ${lock_file}"
  fi
  exec 9>"${lock_file}"
  if ! flock -n 9; then
    echo "Another MailHub certificate sync is already running; skipping."
    exit 0
  fi
fi

if [[ -z "${source_dir}" ]]; then
  source_dir="$(read_env_value MAILHUB_CERT_SOURCE_DIR)"
fi
if [[ -z "${source_dir}" ]]; then
  echo "Certificate sync is not configured; skipping."
  exit 0
fi

source_cert="${source_dir%/}/fullchain.pem"
source_key="${source_dir%/}/privkey.pem"
[[ -r "${source_cert}" ]] || fail "source certificate is not readable: ${source_cert}"
[[ -r "${source_key}" ]] || fail "source private key is not readable: ${source_key}"
[[ ! "${source_cert}" -ef "${source_key}" ]] || fail "source certificate and private key must be different files."

container_cert="$(read_env_value SUBMISSION_TLS_CERT)"
container_key="$(read_env_value SUBMISSION_TLS_KEY)"
mail_hostname="$(read_env_value MAIL_HOSTNAME)"
[[ -n "${container_cert}" ]] || fail "SUBMISSION_TLS_CERT is not configured."
[[ -n "${container_key}" ]] || fail "SUBMISSION_TLS_KEY is not configured."
[[ -n "${mail_hostname}" ]] || fail "MAIL_HOSTNAME is not configured."

target_cert="$(resolve_target "${container_cert}" SUBMISSION_TLS_CERT)"
target_key="$(resolve_target "${container_key}" SUBMISSION_TLS_KEY)"
[[ "${target_cert}" != "${target_key}" ]] \
  || fail "SUBMISSION_TLS_CERT and SUBMISSION_TLS_KEY must point to different files."
[[ ! -L "${target_cert}" ]] || fail "target certificate must not be a symbolic link."
[[ ! -L "${target_key}" ]] || fail "target private key must not be a symbolic link."
[[ ! -L "${target_cert}.previous" ]] || fail "previous certificate backup must not be a symbolic link."
[[ ! -L "${target_key}.previous" ]] || fail "previous private key backup must not be a symbolic link."
[[ ! -e "${target_cert}.previous" || -f "${target_cert}.previous" ]] \
  || fail "previous certificate backup must be a regular file."
[[ ! -e "${target_key}.previous" || -f "${target_key}.previous" ]] \
  || fail "previous private key backup must be a regular file."
resolve_target_ownership

work_dir="$(mktemp -d "${certs_dir}/.cert-sync.XXXXXX")"
chmod 0700 "${work_dir}"
staged_cert="${work_dir}/next-cert.pem"
staged_key="${work_dir}/next-key.pem"
cp -- "${source_cert}" "${staged_cert}"
cp -- "${source_key}" "${staged_key}"
set_target_metadata "${staged_cert}" "${staged_key}"
validate_pair "${staged_cert}" "${staged_key}" source

if [[ -f "${target_cert}" && -f "${target_key}" ]] \
  && cmp -s "${staged_cert}" "${target_cert}" \
  && cmp -s "${staged_key}" "${target_key}"; then
  set_target_metadata "${target_cert}" "${target_key}"
  if [[ "${restart_app}" == "1" ]]; then
    cd "${project_dir}"
    verify_container_access
    live_certificate_current=1
    for port in ${verify_endpoints}; do
      if ! tls_endpoint_matches "${port}"; then
        live_certificate_current=0
        break
      fi
    done
    if [[ "${live_certificate_current}" == "0" ]]; then
      restart_tls_services
      for port in ${verify_endpoints}; do
        verify_tls_endpoint "${port}"
      done
      echo "MailHub TLS files were current; the app was restarted to load and verify them."
      exit 0
    fi
  fi
  echo "MailHub TLS certificate is already up to date."
  exit 0
fi

if [[ -e "${target_cert}" ]]; then
  [[ -f "${target_cert}" ]] || fail "target certificate is not a regular file."
  cp -a -- "${target_cert}" "${work_dir}/backup-cert.pem"
  backup_cert_exists=1
fi
if [[ -e "${target_key}" ]]; then
  [[ -f "${target_key}" ]] || fail "target private key is not a regular file."
  cp -a -- "${target_key}" "${work_dir}/backup-key.pem"
  backup_key_exists=1
fi

rollback_required=1
mv -f -- "${staged_cert}" "${target_cert}"
mv -f -- "${staged_key}" "${target_key}"
validate_pair "${target_cert}" "${target_key}" synchronized

if [[ "${restart_app}" == "1" ]]; then
  cd "${project_dir}"
  verify_container_access
  restart_tls_services
  for port in ${verify_endpoints}; do
    verify_tls_endpoint "${port}"
  done
fi

rollback_required=0
if [[ "${backup_cert_exists}" == "1" ]]; then
  set_file_metadata "${work_dir}/backup-cert.pem" 0644
  mv -f -- "${work_dir}/backup-cert.pem" "${target_cert}.previous"
fi
if [[ "${backup_key_exists}" == "1" ]]; then
  set_file_metadata "${work_dir}/backup-key.pem" 0640
  mv -f -- "${work_dir}/backup-key.pem" "${target_key}.previous"
fi

echo "MailHub TLS certificate synchronized for ${mail_hostname}."
if [[ "${restart_app}" == "1" ]]; then
  echo "MailHub app and Dovecot restarted and verified on TLS endpoints: ${verify_endpoints}."
fi
