#!/bin/sh
set -eu

ENV_FILE="${1:-.env.release}"

if [ ! -f "$ENV_FILE" ]; then
  echo "ERROR: env file not found: $ENV_FILE" >&2
  exit 1
fi

while IFS= read -r raw_line || [ -n "$raw_line" ]; do
  line=$(printf '%s' "$raw_line" | tr -d '\r')
  case "$line" in
    ""|\#*) continue ;;
  esac
  export "$line"
done < "$ENV_FILE"

errors=0
warnings=0

error() {
  errors=$((errors + 1))
  echo "ERROR: $*" >&2
}

warn() {
  warnings=$((warnings + 1))
  echo "WARN: $*" >&2
}

require_non_empty() {
  var_name="$1"
  eval "var_value=\${$var_name:-}"
  if [ -z "$var_value" ]; then
    error "$var_name is required"
  fi
}

reject_values() {
  var_name="$1"
  shift
  eval "var_value=\${$var_name:-}"
  if [ -z "$var_value" ]; then
    return 0
  fi
  for bad in "$@"; do
    if [ "$var_value" = "$bad" ]; then
      error "$var_name still uses placeholder/default value: $bad"
      return 0
    fi
  done
}

reject_patterns() {
  var_name="$1"
  shift
  eval "var_value=\${$var_name:-}"
  if [ -z "$var_value" ]; then
    return 0
  fi
  for pattern in "$@"; do
    case "$var_value" in
      $pattern)
        error "$var_name still uses placeholder/default pattern: $var_value"
        return 0
        ;;
    esac
  done
}

validate_url() {
  var_name="$1"
  eval "var_value=\${$var_name:-}"
  if [ -z "$var_value" ]; then
    return 0
  fi
  case "$var_value" in
    http://*|https://*) ;;
    *) error "$var_name must start with http:// or https://" ;;
  esac
}

validate_no_trailing_slash() {
  var_name="$1"
  eval "var_value=\${$var_name:-}"
  case "$var_value" in
    */) error "$var_name must not have a trailing slash" ;;
  esac
}

validate_boolean() {
  var_name="$1"
  eval "var_value=\${$var_name:-}"
  case "$var_value" in
    ""|true|false) ;;
    *) error "$var_name must be true or false" ;;
  esac
}

validate_port() {
  var_name="$1"
  eval "var_value=\${$var_name:-}"
  if [ -z "$var_value" ]; then
    return 0
  fi
  case "$var_value" in
    *[!0-9]*|"") error "$var_name must be numeric" ;;
    *)
      if [ "$var_value" -lt 1 ] || [ "$var_value" -gt 65535 ]; then
        error "$var_name must be between 1 and 65535"
      fi
      ;;
  esac
}

validate_min_length() {
  var_name="$1"
  min_length="$2"
  eval "var_value=\${$var_name:-}"
  if [ -z "$var_value" ]; then
    return 0
  fi
  if [ "${#var_value}" -lt "$min_length" ]; then
    error "$var_name must be at least $min_length characters"
  fi
}

validate_non_negative_integer() {
  var_name="$1"
  eval "var_value=\${$var_name:-}"
  case "$var_value" in
    "") ;;
    *[!0-9]*) error "$var_name must be a non-negative integer" ;;
  esac
}

validate_redis_nodes() {
  var_name="$1"
  eval "nodes=\${$var_name:-}"
  if [ -z "$nodes" ]; then
    return 0
  fi

  old_ifs="$IFS"
  IFS=","
  for node in $nodes; do
    host=${node%:*}
    port=${node##*:}
    if [ -z "$host" ] || [ "$host" = "$node" ]; then
      error "$var_name entries must use host:port"
      continue
    fi
    case "$host" in
      *[!A-Za-z0-9._-]*)
        error "$var_name contains an invalid host: $host"
        ;;
    esac
    case "$port" in
      *[!0-9]*|"")
        error "$var_name contains an invalid port: $node"
        ;;
      *)
        if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
          error "$var_name port must be between 1 and 65535: $node"
        fi
        ;;
    esac
  done
  IFS="$old_ifs"
}

validate_redis_sentinel_configuration() {
  master="${SPRING_DATA_REDIS_SENTINEL_MASTER:-}"
  nodes="${SPRING_DATA_REDIS_SENTINEL_NODES:-}"

  if [ -n "$master" ] && [ -z "$nodes" ]; then
    error "SPRING_DATA_REDIS_SENTINEL_NODES is required when SPRING_DATA_REDIS_SENTINEL_MASTER is set"
  fi
  if [ -n "$nodes" ] && [ -z "$master" ]; then
    error "SPRING_DATA_REDIS_SENTINEL_MASTER is required when SPRING_DATA_REDIS_SENTINEL_NODES is set"
  fi
  validate_redis_nodes SPRING_DATA_REDIS_SENTINEL_NODES
}

validate_redis_cluster_database() {
  if [ -z "${SPRING_DATA_REDIS_CLUSTER_NODES:-}" ]; then
    return 0
  fi

  case "${SPRING_DATA_REDIS_DATABASE:-0}" in
    0) ;;
    *) error "SPRING_DATA_REDIS_DATABASE must be 0 when SPRING_DATA_REDIS_CLUSTER_NODES is set" ;;
  esac
}

require_non_empty SKILLHUB_PUBLIC_BASE_URL
validate_url SKILLHUB_PUBLIC_BASE_URL
validate_no_trailing_slash SKILLHUB_PUBLIC_BASE_URL

require_non_empty SKILLHUB_DOWNLOAD_ANON_COOKIE_SECRET
reject_values SKILLHUB_DOWNLOAD_ANON_COOKIE_SECRET "change-me-in-production" "replace-me" "replace-with-random-download-secret-32-bytes"
reject_patterns SKILLHUB_DOWNLOAD_ANON_COOKIE_SECRET "TODO_*" "todo_*" "replace*"
validate_min_length SKILLHUB_DOWNLOAD_ANON_COOKIE_SECRET 32

reject_values POSTGRES_PASSWORD "change-this-postgres-password" "skillhub_demo" "skillhub_dev"
reject_patterns POSTGRES_PASSWORD "TODO_*" "todo_*"
reject_values BOOTSTRAP_ADMIN_PASSWORD "replace-this-admin-password" "ChangeMe!2026" "Admin@2026"
reject_patterns BOOTSTRAP_ADMIN_PASSWORD "TODO_*" "todo_*" "replace*"
if [ "${BOOTSTRAP_ADMIN_ENABLED:-false}" = "true" ]; then
  require_non_empty BOOTSTRAP_ADMIN_PASSWORD
fi
reject_values SKILLHUB_STORAGE_S3_ACCESS_KEY "replace-me"
reject_values SKILLHUB_STORAGE_S3_SECRET_KEY "replace-me"
reject_patterns SKILLHUB_STORAGE_S3_ACCESS_KEY "TODO_*" "todo_*" "replace*"
reject_patterns SKILLHUB_STORAGE_S3_SECRET_KEY "TODO_*" "todo_*" "replace*"
reject_patterns SPRING_MAIL_USERNAME "TODO_*" "todo_*" "replace*"
reject_patterns SPRING_MAIL_PASSWORD "TODO_*" "todo_*" "replace*"

validate_boolean SESSION_COOKIE_SECURE
validate_boolean BOOTSTRAP_ADMIN_ENABLED
validate_boolean SKILLHUB_TRUST_FORWARDED_PROTO
validate_boolean SKILLHUB_BUILTIN_SKILLS_ENABLED
validate_boolean SKILLHUB_STORAGE_S3_FORCE_PATH_STYLE
validate_boolean SKILLHUB_STORAGE_S3_AUTO_CREATE_BUCKET
validate_boolean SKILLHUB_AUTH_LDAP_ENABLED
validate_boolean SKILLHUB_AUTH_LDAP_START_TLS
validate_boolean SKILLHUB_AUTH_LDAP_ALLOW_INSECURE_FOR_TESTING
validate_boolean SKILLHUB_AUTH_LDAP_EMAIL_AUTHORITATIVE
validate_boolean SKILLHUB_AUTH_CAS_ENABLED
validate_boolean SKILLHUB_AUTH_CAS_ALLOW_INSECURE_FOR_TESTING
validate_boolean SKILLHUB_AUTH_DINGTALK_ENABLED
validate_boolean SPRING_DATA_REDIS_SSL_ENABLED
validate_boolean SKILLHUB_REDIS_SENTINEL_CHECK_SENTINELS_LIST

if [ "${SKILLHUB_AUTH_LDAP_ENABLED:-false}" = "true" ]; then
  require_non_empty SKILLHUB_AUTH_LDAP_PROVIDER_CODE
  require_non_empty SKILLHUB_AUTH_LDAP_DISPLAY_NAME
  require_non_empty SKILLHUB_AUTH_LDAP_AUTHORITY
  require_non_empty SKILLHUB_AUTH_LDAP_URL
  require_non_empty SKILLHUB_AUTH_LDAP_BASE_DN
  require_non_empty SKILLHUB_AUTH_LDAP_USER_SEARCH_FILTER
  require_non_empty SKILLHUB_AUTH_LDAP_BIND_DN
  require_non_empty SKILLHUB_AUTH_LDAP_BIND_PASSWORD
  case "${SKILLHUB_AUTH_LDAP_DIRECTORY_TYPE:-OPENLDAP}" in
    OPENLDAP|ACTIVE_DIRECTORY|CUSTOM) ;;
    *) error "SKILLHUB_AUTH_LDAP_DIRECTORY_TYPE must be OPENLDAP, ACTIVE_DIRECTORY, or CUSTOM" ;;
  esac
  if [ "${SKILLHUB_AUTH_LDAP_DIRECTORY_TYPE:-OPENLDAP}" = "CUSTOM" ]; then
    require_non_empty SKILLHUB_AUTH_LDAP_SUBJECT_ATTRIBUTE
    require_non_empty SKILLHUB_AUTH_LDAP_SUBJECT_TYPE
  fi
  case "${SKILLHUB_AUTH_LDAP_URL:-}" in
    ldaps://*)
      if [ "${SKILLHUB_AUTH_LDAP_START_TLS:-false}" = "true" ]; then
        error "SKILLHUB_AUTH_LDAP_START_TLS must be false for an ldaps URL"
      fi
      ;;
    ldap://*)
      if [ "${SKILLHUB_AUTH_LDAP_START_TLS:-false}" != "true" ]; then
        error "SKILLHUB_AUTH_LDAP_START_TLS must be true for an ldap URL in release deployments"
      fi
      ;;
    *) error "SKILLHUB_AUTH_LDAP_URL must start with ldap:// or ldaps://" ;;
  esac
  case "${SKILLHUB_AUTH_LDAP_URL:-}" in
    *\?*|*\#*) error "SKILLHUB_AUTH_LDAP_URL must not contain a query or fragment" ;;
  esac
  if [ "${SKILLHUB_AUTH_LDAP_ALLOW_INSECURE_FOR_TESTING:-false}" = "true" ]; then
    error "SKILLHUB_AUTH_LDAP_ALLOW_INSECURE_FOR_TESTING cannot be true in release deployments"
  fi
fi

if [ "${SKILLHUB_AUTH_CAS_ENABLED:-false}" = "true" ]; then
  require_non_empty SKILLHUB_AUTH_CAS_PROVIDER_CODE
  require_non_empty SKILLHUB_AUTH_CAS_DISPLAY_NAME
  require_non_empty SKILLHUB_AUTH_CAS_AUTHORITY
  require_non_empty SKILLHUB_AUTH_CAS_SERVER_URL
  require_non_empty SKILLHUB_AUTH_CAS_SERVICE_URL
  validate_url SKILLHUB_AUTH_CAS_SERVER_URL
  validate_url SKILLHUB_AUTH_CAS_SERVICE_URL
  case "${SKILLHUB_AUTH_CAS_SERVER_URL:-}" in
    *\?*|*\#*) error "SKILLHUB_AUTH_CAS_SERVER_URL must not contain a query or fragment" ;;
  esac
  case "${SKILLHUB_AUTH_CAS_SERVICE_URL:-}" in
    *\?*|*\#*) error "SKILLHUB_AUTH_CAS_SERVICE_URL must not contain a query or fragment" ;;
  esac
  case "${SKILLHUB_AUTH_CAS_SERVER_URL:-}" in
    https://*) ;;
    *) error "SKILLHUB_AUTH_CAS_SERVER_URL must use https in release deployments" ;;
  esac
  case "${SKILLHUB_AUTH_CAS_SERVICE_URL:-}" in
    https://*) ;;
    *) error "SKILLHUB_AUTH_CAS_SERVICE_URL must use https in release deployments" ;;
  esac
  case "${SKILLHUB_AUTH_CAS_PROTOCOL_VERSION:-3.0}" in
    2.0|3.0) ;;
    *) error "SKILLHUB_AUTH_CAS_PROTOCOL_VERSION must be 2.0 or 3.0" ;;
  esac
  expected_cas_callback="/api/v1/auth/cas/${SKILLHUB_AUTH_CAS_PROVIDER_CODE}/callback"
  case "${SKILLHUB_AUTH_CAS_SERVICE_URL:-}" in
    *"$expected_cas_callback") ;;
    *) error "SKILLHUB_AUTH_CAS_SERVICE_URL must end with $expected_cas_callback" ;;
  esac
  if [ "${SKILLHUB_AUTH_CAS_ALLOW_INSECURE_FOR_TESTING:-false}" = "true" ]; then
    error "SKILLHUB_AUTH_CAS_ALLOW_INSECURE_FOR_TESTING cannot be true in release deployments"
  fi
fi

if [ "${SKILLHUB_AUTH_DINGTALK_ENABLED:-false}" = "true" ]; then
  require_non_empty SKILLHUB_AUTH_DINGTALK_AUTHORITY
  require_non_empty SKILLHUB_AUTH_DINGTALK_CONNECT_TIMEOUT
  require_non_empty SKILLHUB_AUTH_DINGTALK_READ_TIMEOUT
  require_non_empty SKILLHUB_AUTH_DINGTALK_MAX_RESPONSE_BYTES
  require_non_empty OAUTH2_DINGTALK_CLIENT_ID
  require_non_empty OAUTH2_DINGTALK_CLIENT_SECRET
  reject_values OAUTH2_DINGTALK_CLIENT_ID "placeholder" "local-placeholder"
  reject_values OAUTH2_DINGTALK_CLIENT_SECRET "placeholder" "local-placeholder"
  case "${SKILLHUB_AUTH_DINGTALK_AUTHORITY:-}" in
    [a-z0-9]*) ;;
    *) error "SKILLHUB_AUTH_DINGTALK_AUTHORITY must start with a lowercase letter or digit" ;;
  esac
  case "${SKILLHUB_AUTH_DINGTALK_AUTHORITY:-}" in
    *[!a-z0-9._:-]*) error "SKILLHUB_AUTH_DINGTALK_AUTHORITY contains invalid characters" ;;
  esac
  validate_non_negative_integer SKILLHUB_AUTH_DINGTALK_MAX_RESPONSE_BYTES
  case "${SKILLHUB_AUTH_DINGTALK_MAX_RESPONSE_BYTES:-}" in
    "") ;;
    *)
      if [ "$SKILLHUB_AUTH_DINGTALK_MAX_RESPONSE_BYTES" -lt 1024 ] \
          || [ "$SKILLHUB_AUTH_DINGTALK_MAX_RESPONSE_BYTES" -gt 1048576 ]; then
        error "SKILLHUB_AUTH_DINGTALK_MAX_RESPONSE_BYTES must be between 1024 and 1048576"
      fi
      ;;
  esac
fi

dingtalk_id="${OAUTH2_DINGTALK_CLIENT_ID:-}"
dingtalk_secret="${OAUTH2_DINGTALK_CLIENT_SECRET:-}"
if [ -n "$dingtalk_id" ] && [ -z "$dingtalk_secret" ]; then
  error "OAUTH2_DINGTALK_CLIENT_SECRET is required when OAUTH2_DINGTALK_CLIENT_ID is set"
fi
if [ -n "$dingtalk_secret" ] && [ -z "$dingtalk_id" ]; then
  error "OAUTH2_DINGTALK_CLIENT_ID is required when OAUTH2_DINGTALK_CLIENT_SECRET is set"
fi

validate_port POSTGRES_PORT
validate_port REDIS_PORT
validate_port API_PORT
validate_port WEB_PORT
validate_non_negative_integer SPRING_DATA_REDIS_CLUSTER_MAX_REDIRECTS
validate_redis_nodes SPRING_DATA_REDIS_CLUSTER_NODES
validate_redis_cluster_database
validate_redis_sentinel_configuration

require_non_empty POSTGRES_DB
require_non_empty POSTGRES_USER
require_non_empty POSTGRES_PASSWORD

storage_provider="${SKILLHUB_STORAGE_PROVIDER:-}"
case "$storage_provider" in
  s3)
    require_non_empty SKILLHUB_STORAGE_S3_ENDPOINT
    require_non_empty SKILLHUB_STORAGE_S3_BUCKET
    require_non_empty SKILLHUB_STORAGE_S3_ACCESS_KEY
    require_non_empty SKILLHUB_STORAGE_S3_SECRET_KEY
    require_non_empty SKILLHUB_STORAGE_S3_REGION
    validate_url SKILLHUB_STORAGE_S3_ENDPOINT
    validate_url SKILLHUB_STORAGE_S3_PUBLIC_ENDPOINT
    ;;
  local)
    warn "SKILLHUB_STORAGE_PROVIDER=local is only suitable for non-production or temporary validation"
    ;;
  "")
    error "SKILLHUB_STORAGE_PROVIDER is required"
    ;;
  *)
    error "SKILLHUB_STORAGE_PROVIDER must be either local or s3"
    ;;
esac

if [ -n "${SKILLHUB_WEB_API_BASE_URL:-}" ]; then
  validate_url SKILLHUB_WEB_API_BASE_URL
  validate_no_trailing_slash SKILLHUB_WEB_API_BASE_URL
fi

if [ -n "${DEVICE_AUTH_VERIFICATION_URI:-}" ]; then
  validate_url DEVICE_AUTH_VERIFICATION_URI
fi

if [ "${SESSION_COOKIE_SECURE:-true}" != "true" ]; then
  warn "SESSION_COOKIE_SECURE is not true; only acceptable behind plain HTTP during temporary local verification"
fi

if [ "${POSTGRES_BIND_ADDRESS:-127.0.0.1}" != "127.0.0.1" ]; then
  warn "POSTGRES_BIND_ADDRESS is not 127.0.0.1; confirm database exposure is intended"
fi

if [ "${REDIS_BIND_ADDRESS:-127.0.0.1}" != "127.0.0.1" ]; then
  warn "REDIS_BIND_ADDRESS is not 127.0.0.1; confirm Redis exposure is intended"
fi

oauth_id="${OAUTH2_GITHUB_CLIENT_ID:-}"
oauth_secret="${OAUTH2_GITHUB_CLIENT_SECRET:-}"
if [ -n "$oauth_id" ] && [ -z "$oauth_secret" ]; then
  error "OAUTH2_GITHUB_CLIENT_SECRET is required when OAUTH2_GITHUB_CLIENT_ID is set"
fi
if [ -n "$oauth_secret" ] && [ -z "$oauth_id" ]; then
  error "OAUTH2_GITHUB_CLIENT_ID is required when OAUTH2_GITHUB_CLIENT_SECRET is set"
fi

if [ "$errors" -gt 0 ]; then
  echo "Release config validation failed: $errors error(s), $warnings warning(s)." >&2
  exit 1
fi

echo "Release config validation passed with $warnings warning(s)."
