#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
DEPLOY_HELPER="${SCRIPT_DIR}/deploy-enterprise.sh"
DB_MIGRATION_HELPER="${SCRIPT_DIR}/run-db-migrations.sh"
TRANSPORT_LIB="${SCRIPT_DIR}/lib/transport.sh"
DEFAULT_REMOTE_APP_DIR="/opt/security-center/enterprise"
DEFAULT_REMOTE_SOURCE_DIR="${DEFAULT_REMOTE_APP_DIR}/source"
DEFAULT_REMOTE_BUILD_TIMEOUT_SECONDS="1800"

# shellcheck disable=SC1090
source "${TRANSPORT_LIB}"

usage() {
  cat <<'EOF'
Remote deploy helper for scripts/enterprise.

This script does four things:
1. validates and renders the enterprise bundle locally
2. uploads the rendered bundle to the target EC2 host
3. runs the database migration phase on the remote host before backend startup
4. runs docker compose on the remote host

Optional build mode:
- upload backend/frontend source to the EC2 host
- build Docker images on the EC2 host
- then run docker compose using those freshly built image tags

Usage:
  ./scripts/enterprise/remote-deploy-enterprise.sh \
    --env-file ./scripts/enterprise/enterprise.env.example \
    --instance-id i-0123456789abcdef0

Options:
  --env-file <path>        Required. Enterprise env file used as source of truth.
  --transport <mode>       Optional. ssm (default) or ssh.
  --instance-id <id>       Required for ssm unless provided via env.
  --host <user@host>       Required for ssh unless provided via env.
  --remote-dir <path>      Optional. Default: /opt/security-center/enterprise
  --source-dir <path>      Optional. Local repo source directory for remote builds.
                           Default: current repository root.
  --identity-file <path>   Optional. SSH private key.
  --region <name>          AWS region for ssm and S3 staging.
  --profile <name>         AWS CLI profile override.
  --s3-bucket <name>       S3 bucket used for artifact staging in ssm mode.
  --s3-prefix <prefix>     S3 object prefix used for artifact staging in ssm mode.
  --resolve-ssm            Resolve SSM parameters locally before upload.
  --build-on-remote        Upload source and build backend/frontend images on the EC2 host.
  --skip-build             With --build-on-remote, skip docker build and reuse remote source/images.
  --skip-upload            Reuse an already uploaded rendered bundle on the remote host.
  --skip-db-migrations     Skip the database migration phase before backend startup.
  --allow-schema-drift     Override the conservative schema-sensitive release gate.
  --skip-up                Upload/prepare only. Do not run docker compose up.
  --dry-run                Print what would happen without executing remote changes.
  --help                   Show this help.

Requirements:
  - aws CLI for ssm mode
  - ssh/scp for ssh mode
  - aws s3 cp for ssm artifact staging
  - tar
  - docker compose on the remote host
  - aws CLI on the remote host for ssm mode artifact download
EOF
}

die() {
  echo "ERROR: $*" >&2
  exit 1
}

info() {
  echo "INFO: $*"
}

warn() {
  echo "WARN: $*" >&2
}

format_elapsed_seconds() {
  local seconds="${1:-0}"
  local minutes=$((seconds / 60))
  local remainder=$((seconds % 60))
  if (( minutes > 0 )); then
    printf '%sm%ss' "${minutes}" "${remainder}"
  else
    printf '%ss' "${remainder}"
  fi
}

run_remote_phase() {
  local label="$1"
  shift
  local started_at finished_at elapsed
  started_at="$(date +%s)"
  info "starting: ${label}"
  "$@"
  finished_at="$(date +%s)"
  elapsed=$((finished_at - started_at))
  info "completed: ${label} ($(format_elapsed_seconds "${elapsed}"))"
}

require_bin() {
  command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

rendered_runtime_env_has_placeholders() {
  local path="$1"
  grep -q '__RESOLVE_FROM_SSM__' "$path"
}

verify_remote_runtime_env_resolved() {
  local remote_env_path="$1"
  local placeholder_state=""

  if [[ "${DRY_RUN}" == "true" ]]; then
    echo "DRY-RUN remote verify ($(sc_transport_target_label) via ${TRANSPORT}): ${remote_env_path} has no __RESOLVE_FROM_SSM__ placeholders"
    return 0
  fi

  sc_transport_run "test -f '${remote_env_path}'" >/dev/null
  placeholder_state="$(sc_transport_run "sh -lc 'if grep -q \"__RESOLVE_FROM_SSM__\" \"${remote_env_path}\"; then echo present; else echo absent; fi'")"
  if [[ "${placeholder_state}" == *present* ]]; then
    die "remote runtime env still contains unresolved SSM placeholders: ${remote_env_path}"
  fi
}

runtime_env_value() {
  local env_path="$1"
  local key="$2"
  awk -F= -v key="${key}" '$1 == key {print substr($0, length(key) + 2); exit}' "${env_path}"
}

image_reference_is_mutable() {
  local image_ref="$1"
  [[ -n "${image_ref}" ]] || return 0
  [[ "${image_ref}" == *@* ]] && return 1

  local remainder="${image_ref##*/}"
  if [[ "${remainder}" != *:* ]]; then
    return 0
  fi

  local tag="${remainder##*:}"
  [[ "${tag}" == "latest" ]]
}

enforce_rendered_image_policy() {
  local runtime_env_path="$1"
  local environment_mode backend_image frontend_image

  environment_mode="$(runtime_env_value "${runtime_env_path}" "SC_ENVIRONMENT_MODE")"
  [[ "${environment_mode}" == "prod" ]] || return 0
  [[ "${BUILD_ON_REMOTE}" != "true" ]] || return 0

  backend_image="$(runtime_env_value "${runtime_env_path}" "SC_BACKEND_IMAGE")"
  frontend_image="$(runtime_env_value "${runtime_env_path}" "SC_FRONTEND_IMAGE")"

  if image_reference_is_mutable "${backend_image}" || image_reference_is_mutable "${frontend_image}"; then
    die "production remote deploy with prebuilt images requires immutable backend/frontend references. Use explicit version tags or digests, or use --build-on-remote to build this release on the target host."
  fi
}

validate_rendered_prod_runtime_paths() {
  local runtime_env_path="$1"
  local render_dir="$2"
  local environment_mode cert_path key_path

  environment_mode="$(runtime_env_value "${runtime_env_path}" "SC_ENVIRONMENT_MODE")"
  [[ "${environment_mode}" == "prod" ]] || return 0

  cert_path="$(runtime_env_value "${runtime_env_path}" "SC_TLS_CERT_FULLCHAIN")"
  key_path="$(runtime_env_value "${runtime_env_path}" "SC_TLS_CERT_PRIVKEY")"

  if [[ "${cert_path}" == "${render_dir}"/* || "${key_path}" == "${render_dir}"/* ]]; then
    die "production render produced local TLS paths under ${render_dir}. Use real remote certificate paths such as /etc/letsencrypt/live/<host>/fullchain.pem."
  fi
  if [[ "${cert_path}" == /tmp/* || "${key_path}" == /tmp/* || "${cert_path}" == /Users/* || "${key_path}" == /Users/* ]]; then
    die "production render contains local-looking TLS paths (${cert_path}, ${key_path}). Refusing to upload an unsafe rendered bundle."
  fi
}

run_local() {
  if [[ "${DRY_RUN}" == "true" ]]; then
    echo "DRY-RUN local (executed for local verification): $*"
  fi
  "$@"
}

pull_remote_application_images() {
  local compose_cmd="cd '${REMOTE_DIR}/rendered' && docker compose --env-file .env.runtime -f docker-compose.enterprise.yml pull backend frontend"
  if [[ "${DRY_RUN}" == "true" ]]; then
    echo "DRY-RUN remote pull ($(sc_transport_target_label) via ${TRANSPORT}): backend/frontend images from registry"
    return 0
  fi
  sc_transport_run "${compose_cmd}"
}

stop_remote_application_services_for_migration() {
  local stop_cmd="cd '${REMOTE_DIR}/rendered' && docker compose --env-file .env.runtime -f docker-compose.enterprise.yml stop backend frontend nginx || true"
  if [[ "${DRY_RUN}" == "true" ]]; then
    echo "DRY-RUN remote stop ($(sc_transport_target_label) via ${TRANSPORT}): backend frontend nginx before migration"
    return 0
  fi
  sc_transport_run "${stop_cmd}"
}

show_remote_application_images() {
  local compose_ps="cd '${REMOTE_DIR}/rendered' && docker compose --env-file .env.runtime -f docker-compose.enterprise.yml ps"
  local compose_images="cd '${REMOTE_DIR}/rendered' && docker compose --env-file .env.runtime -f docker-compose.enterprise.yml images backend frontend"
  local inspect_cmd="cd '${REMOTE_DIR}/rendered' && sh -lc 'ids=\$(docker compose --env-file .env.runtime -f docker-compose.enterprise.yml ps -q backend frontend); if [[ -z \"\$ids\" ]]; then echo \"no backend/frontend containers found\"; exit 0; fi; docker inspect --format \"{{.Name}} image={{.Config.Image}} image_id={{.Image}}\" \$ids'"

  if [[ "${DRY_RUN}" == "true" ]]; then
    echo "DRY-RUN remote inspect ($(sc_transport_target_label) via ${TRANSPORT}): compose status, compose images, and backend/frontend image IDs"
    return 0
  fi

  sc_transport_run "${compose_ps}"
  sc_transport_run "${compose_images}"
  sc_transport_run "${inspect_cmd}"
}

wait_remote_mysql_ready() {
  local wait_cmd
  wait_cmd="cd '${REMOTE_DIR}/rendered' && for i in \$(seq 1 60); do if docker compose --env-file .env.runtime -f docker-compose.enterprise.yml exec -T mysql sh -lc 'mysqladmin ping -h 127.0.0.1 -p\"\${MYSQL_ROOT_PASSWORD}\" >/dev/null 2>&1'; then echo mysql-ready; exit 0; fi; sleep 5; done; echo 'mysql did not become ready within 300s' >&2; exit 1"
  if [[ "${DRY_RUN}" == "true" ]]; then
    echo "DRY-RUN remote wait ($(sc_transport_target_label) via ${TRANSPORT}): mysql accepts connections before migrations"
    return 0
  fi
  sc_transport_run "${wait_cmd}"
}

# verify_remote_service_health <service> <port> <runtime-env-key> <default-path>
# 远端重试循环（12 次 × 10s ≈ 2 分钟）探测容器内健康端点，与
# wait_remote_mysql_ready 一样把循环放在远端、单次 transport 会话内完成等待。
verify_remote_service_health() {
  local service="$1" port="$2" path_key="$3" default_path="$4"
  local health_path
  health_path="$(runtime_env_value "${RENDER_DIR}/.env.runtime" "${path_key}")"
  health_path="${health_path:-${default_path}}"
  local verify_cmd="cd '${REMOTE_DIR}/rendered' && for i in \$(seq 1 12); do if docker compose --env-file .env.runtime -f docker-compose.enterprise.yml exec -T ${service} sh -lc 'wget -q -O - http://localhost:${port}${health_path} >/dev/null 2>&1'; then echo ${service}-healthy; exit 0; fi; sleep 10; done; echo '${service} did not become healthy within 120s' >&2; exit 1"
  if [[ "${DRY_RUN}" == "true" ]]; then
    echo "DRY-RUN remote verify ($(sc_transport_target_label) via ${TRANSPORT}): ${service} responds on ${health_path}"
    return 0
  fi
  sc_transport_run "${verify_cmd}"
}

is_schema_sensitive_path() {
  case "$1" in
    *_test.go)
      return 1
      ;;
    security-center-backend/internal/model/*)
      return 0
      ;;
    security-center-backend/internal/modules/*/model/*)
      return 0
      ;;
    security-center-backend/internal/infra/mysql/*)
      return 0
      ;;
    security-center-backend/migrations/*)
      return 0
      ;;
    scripts/enterprise/mysql-init.sql)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

is_new_migration_file() {
  case "$1" in
    security-center-backend/migrations/*.sql)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

enforce_schema_migration_gate() {
  if [[ "${SC_SCHEMA_GATE_CHECKED:-false}" == "true" ]]; then
    info "schema-sensitive release gate already checked by the parent entrypoint"
    return 0
  fi

  if [[ "${ALLOW_SCHEMA_DRIFT}" == "true" ]]; then
    warn "schema-sensitive release gate overridden by --allow-schema-drift"
    return 0
  fi

  require_bin git

  local -a sensitive_paths=()
  local -a migration_files=()
  local line status path

  while IFS= read -r line; do
    [[ -z "${line}" ]] && continue
    status="${line:0:2}"
    path="${line:3}"
    path="${path%% -> *}"
    if is_schema_sensitive_path "${path}"; then
      sensitive_paths+=("${path}")
    fi
    case "${status}" in
      A*|\?\?)
        if is_new_migration_file "${path}"; then
          migration_files+=("${path}")
        fi
        ;;
    esac
  done < <(git -C "${ROOT_DIR}" status --porcelain=v1 --untracked-files=normal)

  if ((${#sensitive_paths[@]} == 0)); then
    info "schema-sensitive release gate: no backend schema-sensitive changes detected"
    return 0
  fi

  if ((${#migration_files[@]} == 0)); then
    printf 'INFO: schema-sensitive release gate saw these paths:\n' >&2
    printf 'INFO:   - %s\n' "${sensitive_paths[@]}" >&2
    die "schema-sensitive changes detected without a new migration file; add a new SQL migration under security-center-backend/migrations or rerun with --allow-schema-drift for an explicit emergency override"
  fi

  info "schema-sensitive release gate: sensitive paths detected and new migration files present"
  info "  - sensitive paths: ${sensitive_paths[*]}"
  info "  - migration files: ${migration_files[*]}"
}

print_dir_manifest() {
  local source_dir="$1"
  if [[ ! -d "${source_dir}" ]]; then
    return 0
  fi
  (
    cd "${source_dir}"
    find . -mindepth 1 -print | LC_ALL=C sort
  ) | sed 's#^\./##'
}

copy_path_into_stage() {
  local source_root="$1"
  local stage_root="$2"
  local relative_path="$3"
  local source_path="${source_root}/${relative_path}"
  local dest_path="${stage_root}/${relative_path}"

  [[ -e "${source_path}" ]] || return 0

  mkdir -p "$(dirname "${dest_path}")"
  if [[ -d "${source_path}" ]]; then
    cp -R "${source_path}" "${dest_path}"
  else
    cp "${source_path}" "${dest_path}"
  fi
}

create_archive_from_dir() {
  local source_dir="$1"
  local archive_path="$2"
  tar -C "${source_dir}" -czf "${archive_path}" .
}

create_remote_build_archive() {
  local source_root="$1"
  local archive_path="$2"
  local stage_root="$3"
  local -a backend_allowlist=(
    "security-center-backend/Dockerfile"
    "security-center-backend/.dockerignore"
    "security-center-backend/go.mod"
    "security-center-backend/go.sum"
    "security-center-backend/cmd"
    "security-center-backend/internal"
    "security-center-backend/configs/config.example.yaml"
  )
  local -a frontend_allowlist=(
    "security-center-frontend/Dockerfile"
    "security-center-frontend/.dockerignore"
    "security-center-frontend/package.json"
    "security-center-frontend/package-lock.json"
    "security-center-frontend/next.config.js"
    "security-center-frontend/next-env.d.ts"
    "security-center-frontend/tsconfig.json"
    "security-center-frontend/app"
    "security-center-frontend/components"
    "security-center-frontend/lib"
    "security-center-frontend/styles"
    "security-center-frontend/store"
    "security-center-frontend/types"
    "security-center-frontend/public"
  )
  local path=""

  [[ -d "${source_root}/security-center-backend" ]] || die "remote-build source root missing security-center-backend: ${source_root}"
  [[ -d "${source_root}/security-center-frontend" ]] || die "remote-build source root missing security-center-frontend: ${source_root}"

  rm -rf "${stage_root}"
  mkdir -p "${stage_root}"

  for path in "${backend_allowlist[@]}"; do
    copy_path_into_stage "${source_root}" "${stage_root}" "${path}"
  done
  for path in "${frontend_allowlist[@]}"; do
    copy_path_into_stage "${source_root}" "${stage_root}" "${path}"
  done

  find "${stage_root}" -type f \( -name '.env' -o -name '.env.*' \) -delete
  rm -f "${stage_root}/security-center-backend/configs/config.yaml"

  tar -C "${stage_root}" -czf "${archive_path}" .
}

prepare_render_bundle_for_migrations() {
  local render_root="$1"
  local source_runner="${DB_MIGRATION_HELPER}"
  local target_runner_dir="${render_root}/scripts/enterprise"
  local target_migrations_dir="${render_root}/security-center-backend"

  [[ -f "${source_runner}" ]] || die "database migration helper not found: ${source_runner}"
  [[ -d "${ROOT_DIR}/security-center-backend/migrations" ]] || die "database migrations directory not found: ${ROOT_DIR}/security-center-backend/migrations"

  mkdir -p "${target_runner_dir}" "${target_migrations_dir}"
  cp "${source_runner}" "${target_runner_dir}/run-db-migrations.sh"
  chmod 755 "${target_runner_dir}/run-db-migrations.sh"
  rm -rf "${target_migrations_dir}/migrations"
  cp -R "${ROOT_DIR}/security-center-backend/migrations" "${target_migrations_dir}/"
}

upload_and_extract_archive() {
  local archive_path="$1"
  local target_dir="$2"
  local remote_archive="$3"
  local staging_dir="${target_dir}.incoming"
  case "${TRANSPORT}" in
    ssh)
      if [[ "${DRY_RUN}" == "true" ]]; then
        echo "DRY-RUN upload archive: ${archive_path} -> ${REMOTE_HOST}:${remote_archive}"
        echo "DRY-RUN extract archive into: ${REMOTE_HOST}:${target_dir}"
        return 0
      fi
      local -a scp_cmd=(scp)
      [[ -n "${IDENTITY_FILE}" ]] && scp_cmd+=(-i "${IDENTITY_FILE}")
      sc_transport_run "mkdir -p '$(dirname "${remote_archive}")'"
      scp_cmd+=("${archive_path}" "${REMOTE_HOST}:${remote_archive}")
      "${scp_cmd[@]}"
      sc_transport_run "rm -rf '${staging_dir}' && mkdir -p '${staging_dir}' && tar -xzf '${remote_archive}' -C '${staging_dir}' && rm -f '${remote_archive}' && rm -rf '${target_dir}' && mv '${staging_dir}' '${target_dir}'"
      ;;
    ssm)
      local s3_key="${S3_PREFIX%/}/$(basename "${remote_archive}")"
      if [[ "${DRY_RUN}" == "true" ]]; then
        echo "DRY-RUN upload archive: ${archive_path} -> s3://${S3_BUCKET}/${s3_key}"
        echo "DRY-RUN remote download/extract (${INSTANCE_ID}): ${target_dir}"
        return 0
      fi
      aws s3 cp "${archive_path}" "s3://${S3_BUCKET}/${s3_key}" --region "${AWS_REGION_NAME}" >/dev/null
      sc_transport_run "mkdir -p '$(dirname "${remote_archive}")' && aws s3 cp 's3://${S3_BUCKET}/${s3_key}' '${remote_archive}' && rm -rf '${staging_dir}' && mkdir -p '${staging_dir}' && tar -xzf '${remote_archive}' -C '${staging_dir}' && rm -f '${remote_archive}' && rm -rf '${target_dir}' && mv '${staging_dir}' '${target_dir}'"
      ;;
  esac
}

ENV_FILE=""
TRANSPORT="ssm"
INSTANCE_ID=""
REMOTE_HOST=""
REMOTE_DIR="${DEFAULT_REMOTE_APP_DIR}"
SOURCE_DIR="${ROOT_DIR}"
REMOTE_SOURCE_DIR="${DEFAULT_REMOTE_SOURCE_DIR}"
IDENTITY_FILE=""
AWS_REGION_NAME="${AWS_REGION:-${AWS_DEFAULT_REGION:-}}"
AWS_PROFILE_NAME="${AWS_PROFILE:-}"
S3_BUCKET=""
S3_PREFIX=""
RESOLVE_SSM="false"
BUILD_ON_REMOTE="false"
SKIP_BUILD="false"
SKIP_UPLOAD="false"
SKIP_UP="false"
SKIP_DB_MIGRATIONS="false"
ALLOW_SCHEMA_DRIFT="false"
DRY_RUN="false"
REMOTE_BUILD_TIMEOUT_SECONDS="${SC_REMOTE_BUILD_TIMEOUT_SECONDS:-${DEFAULT_REMOTE_BUILD_TIMEOUT_SECONDS}}"

while (($# > 0)); do
  case "$1" in
    --env-file)
      ENV_FILE="$2"
      shift 2
      ;;
    --transport)
      TRANSPORT="$2"
      shift 2
      ;;
    --instance-id)
      INSTANCE_ID="$2"
      shift 2
      ;;
    --host)
      REMOTE_HOST="$2"
      shift 2
      ;;
    --remote-dir)
      REMOTE_DIR="$2"
      shift 2
      ;;
    --source-dir)
      SOURCE_DIR="$2"
      shift 2
      ;;
    --identity-file)
      IDENTITY_FILE="$2"
      shift 2
      ;;
    --region)
      AWS_REGION_NAME="$2"
      shift 2
      ;;
    --profile)
      AWS_PROFILE_NAME="$2"
      shift 2
      ;;
    --s3-bucket)
      S3_BUCKET="$2"
      shift 2
      ;;
    --s3-prefix)
      S3_PREFIX="$2"
      shift 2
      ;;
    --resolve-ssm)
      RESOLVE_SSM="true"
      shift
      ;;
    --build-on-remote)
      BUILD_ON_REMOTE="true"
      shift
      ;;
    --skip-build)
      SKIP_BUILD="true"
      shift
      ;;
    --skip-upload)
      SKIP_UPLOAD="true"
      shift
      ;;
    --skip-db-migrations)
      SKIP_DB_MIGRATIONS="true"
      shift
      ;;
    --allow-schema-drift)
      ALLOW_SCHEMA_DRIFT="true"
      shift
      ;;
    --skip-up)
      SKIP_UP="true"
      shift
      ;;
    --dry-run)
      DRY_RUN="true"
      shift
      ;;
    --remote-build-timeout-seconds)
      REMOTE_BUILD_TIMEOUT_SECONDS="$2"
      shift 2
      ;;
    --help|-h)
      usage
      exit 0
      ;;
    *)
      die "unknown argument: $1"
      ;;
  esac
done

[[ -n "${ENV_FILE}" ]] || die "--env-file is required"
[[ -d "${SOURCE_DIR}" ]] || die "--source-dir does not exist: ${SOURCE_DIR}"

require_bin bash
require_bin tar
case "${TRANSPORT}" in
  ssh)
    require_bin ssh
    require_bin scp
    [[ -n "${REMOTE_HOST}" ]] || die "--host is required for --transport ssh"
    ;;
  ssm)
    require_bin aws
    [[ -n "${INSTANCE_ID}" ]] || die "--instance-id is required for --transport ssm"
    [[ -n "${AWS_REGION_NAME}" ]] || die "--region is required for --transport ssm"
    [[ -n "${S3_BUCKET}" ]] || die "--s3-bucket is required for --transport ssm"
    ;;
  *)
    die "unsupported transport: ${TRANSPORT}"
    ;;
esac

SC_TRANSPORT_MODE="${TRANSPORT}"
SC_TRANSPORT_DRY_RUN="${DRY_RUN}"
SC_TRANSPORT_REMOTE_HOST="${REMOTE_HOST}"
SC_TRANSPORT_INSTANCE_ID="${INSTANCE_ID}"
SC_TRANSPORT_IDENTITY_FILE="${IDENTITY_FILE}"
SC_TRANSPORT_AWS_REGION="${AWS_REGION_NAME}"
SC_TRANSPORT_AWS_PROFILE="${AWS_PROFILE_NAME}"
run_remote_phase "schema-sensitive release gate" enforce_schema_migration_gate
sc_transport_init

RENDER_DIR="$(mktemp -d /tmp/security-center-enterprise-deploy.XXXXXX)"
trap 'rm -rf "${RENDER_DIR}"' EXIT

run_remote_phase "validate enterprise env" run_local "${DEPLOY_HELPER}" --validate --env-file "${ENV_FILE}"

if [[ "${RESOLVE_SSM}" == "true" ]]; then
  run_remote_phase "render enterprise bundle" run_local "${DEPLOY_HELPER}" --render --env-file "${ENV_FILE}" --output-dir "${RENDER_DIR}" --resolve-ssm
else
  run_remote_phase "render enterprise bundle" run_local "${DEPLOY_HELPER}" --render --env-file "${ENV_FILE}" --output-dir "${RENDER_DIR}"
fi

if rendered_runtime_env_has_placeholders "${RENDER_DIR}/.env.runtime"; then
  if [[ "${DRY_RUN}" == "true" ]]; then
    warn "rendered runtime env still contains unresolved SSM placeholders; continuing because this is a dry-run only"
  else
    die "rendered runtime env still contains unresolved SSM placeholders; rerun deploy with --resolve-ssm or use render-only workflows intentionally"
  fi
fi

enforce_rendered_image_policy "${RENDER_DIR}/.env.runtime"
validate_rendered_prod_runtime_paths "${RENDER_DIR}/.env.runtime" "${RENDER_DIR}"
prepare_render_bundle_for_migrations "${RENDER_DIR}"

RENDER_ARCHIVE="${RENDER_DIR}.tar.gz"
SOURCE_ARCHIVE="${RENDER_DIR}.source.tar.gz"
SOURCE_STAGE_DIR="${RENDER_DIR}.source-stage"

if [[ "${SKIP_UPLOAD}" != "true" ]]; then
  info "preparing remote directories"
  sc_transport_run "mkdir -p '${REMOTE_DIR}/rendered' '${REMOTE_DIR}/releases'"

  info "rendered bundle contents:"
  print_dir_manifest "${RENDER_DIR}"

  if [[ "${DRY_RUN}" != "true" ]]; then
    create_archive_from_dir "${RENDER_DIR}" "${RENDER_ARCHIVE}"
  fi
  run_remote_phase "upload rendered bundle" upload_and_extract_archive "${RENDER_ARCHIVE}" "${REMOTE_DIR}/rendered" "${REMOTE_DIR}/releases/rendered-bundle.tar.gz"
  run_remote_phase "rewrite remote test TLS paths" sc_transport_run "cd '${REMOTE_DIR}/rendered' && if [[ -f tls/fullchain.pem && -f tls/privkey.pem ]]; then sed -i 's#/tmp/security-center-enterprise-deploy[^ ]*/tls/fullchain.pem#${REMOTE_DIR}/rendered/tls/fullchain.pem#g' .env.runtime nginx-security-center.conf; sed -i 's#/tmp/security-center-enterprise-deploy[^ ]*/tls/privkey.pem#${REMOTE_DIR}/rendered/tls/privkey.pem#g' .env.runtime nginx-security-center.conf; fi"
fi

if [[ "${BUILD_ON_REMOTE}" == "true" ]]; then
  if [[ "${SKIP_UPLOAD}" == "true" && "${SKIP_BUILD}" != "true" ]]; then
    die "--build-on-remote --skip-upload would build from the existing remote source tree. Use --skip-build to reuse the current remote image, or rerun without --skip-upload to upload current source."
  fi

  if [[ "${SKIP_UPLOAD}" != "true" ]]; then
    info "preparing remote source directory"
    sc_transport_run "mkdir -p '${REMOTE_SOURCE_DIR}'"

    info "remote-build upload set:"
    printf '%s\n' "security-center-backend" "security-center-frontend"

    if [[ "${DRY_RUN}" != "true" ]]; then
      create_remote_build_archive "${SOURCE_DIR}" "${SOURCE_ARCHIVE}" "${SOURCE_STAGE_DIR}"
    fi
    printf '%s\n' \
      "  - security-center-backend/Dockerfile" \
      "  - security-center-backend/.dockerignore" \
      "  - security-center-backend/go.mod" \
      "  - security-center-backend/go.sum" \
      "  - security-center-backend/cmd/**" \
      "  - security-center-backend/internal/**" \
      "  - security-center-backend/configs/config.example.yaml" \
      "  - security-center-frontend/Dockerfile" \
      "  - security-center-frontend/.dockerignore" \
      "  - security-center-frontend/package.json" \
      "  - security-center-frontend/package-lock.json" \
      "  - security-center-frontend/next.config.js" \
      "  - security-center-frontend/next-env.d.ts" \
      "  - security-center-frontend/tsconfig.json" \
      "  - security-center-frontend/app/**" \
      "  - security-center-frontend/components/**" \
      "  - security-center-frontend/lib/**" \
      "  - security-center-frontend/styles/**" \
      "  - security-center-frontend/store/**" \
      "  - security-center-frontend/types/**" \
      "  - security-center-frontend/public/** (if present)"
    run_remote_phase "upload application source for remote build" upload_and_extract_archive "${SOURCE_ARCHIVE}" "${REMOTE_SOURCE_DIR}" "${REMOTE_DIR}/releases/remote-build-source.tar.gz"
  fi

  if [[ "${SKIP_BUILD}" != "true" ]]; then
    remote_timeout_wrapper=""
    backend_log="${REMOTE_DIR}/logs/backend-build.log"
    frontend_log="${REMOTE_DIR}/logs/frontend-build.log"
    read -r -d '' remote_timeout_wrapper <<EOF || true
if command -v timeout >/dev/null 2>&1; then
  timeout ${REMOTE_BUILD_TIMEOUT_SECONDS}s "\$@"
else
  "\$@"
fi
EOF
    sc_transport_run "mkdir -p '${REMOTE_DIR}/logs'"

    run_remote_phase "build backend image on remote host" sc_transport_run "backend_log='${backend_log}' && rm -f \"\${backend_log}\" && bash -lc $(printf '%q' "${remote_timeout_wrapper}") -- docker build -t \"\$(awk -F= '/^SC_BACKEND_IMAGE=/{print \$2}' '${REMOTE_DIR}/rendered/.env.runtime')\" -f '${REMOTE_SOURCE_DIR}/security-center-backend/Dockerfile' '${REMOTE_SOURCE_DIR}/security-center-backend' > \"\${backend_log}\" 2>&1; rc=\$?; tail -n 200 \"\${backend_log}\" || true; exit \$rc"

    run_remote_phase "build frontend image on remote host" sc_transport_run "frontend_log='${frontend_log}' && rm -f \"\${frontend_log}\" && bash -lc $(printf '%q' "${remote_timeout_wrapper}") -- docker build --build-arg API_PROXY_TARGET=http://backend:8080 --build-arg \"NEXT_PUBLIC_SSO_PROVIDER=\$(awk -F= '/^SC_NEXT_PUBLIC_SSO_PROVIDER=/{print \$2}' '${REMOTE_DIR}/rendered/.env.runtime')\" -t \"\$(awk -F= '/^SC_FRONTEND_IMAGE=/{print \$2}' '${REMOTE_DIR}/rendered/.env.runtime')\" -f '${REMOTE_SOURCE_DIR}/security-center-frontend/Dockerfile' '${REMOTE_SOURCE_DIR}/security-center-frontend' > \"\${frontend_log}\" 2>&1; rc=\$?; tail -n 200 \"\${frontend_log}\" || true; exit \$rc"
  fi
fi

if [[ "${SKIP_UP}" != "true" ]]; then
  if [[ "${SKIP_UPLOAD}" == "true" ]]; then
    run_remote_phase "verify existing remote runtime env" verify_remote_runtime_env_resolved "${REMOTE_DIR}/rendered/.env.runtime"
  fi

  if [[ "${BUILD_ON_REMOTE}" != "true" ]]; then
    run_remote_phase "pull application images" pull_remote_application_images
  fi

  if [[ "${SKIP_DB_MIGRATIONS}" != "true" ]]; then
    run_remote_phase "stop application services before migration" stop_remote_application_services_for_migration
    run_remote_phase "start database services" sc_transport_run "cd '${REMOTE_DIR}/rendered' && docker compose --env-file .env.runtime -f docker-compose.enterprise.yml up -d mysql redis"
    run_remote_phase "wait for mysql readiness" wait_remote_mysql_ready
    remote_migration_cmd="cd '${REMOTE_DIR}/rendered' && ./scripts/enterprise/run-db-migrations.sh --env-file ./.env.runtime --migrations-dir ./security-center-backend/migrations --mysql-prefix 'docker compose --env-file .env.runtime -f docker-compose.enterprise.yml exec -T mysql mysql'"
    run_remote_phase "apply database migrations" sc_transport_run "${remote_migration_cmd}"
    run_remote_phase "start application services" sc_transport_run "cd '${REMOTE_DIR}/rendered' && docker compose --env-file .env.runtime -f docker-compose.enterprise.yml up -d backend frontend nginx"
    run_remote_phase "verify backend health endpoint" verify_remote_service_health backend 8080 SC_BACKEND_HEALTHCHECK_PATH /health
    run_remote_phase "verify frontend health endpoint" verify_remote_service_health frontend 3000 SC_FRONTEND_HEALTHCHECK_PATH /api/health
  else
    run_remote_phase "start remote docker compose stack" sc_transport_run "cd '${REMOTE_DIR}/rendered' && docker compose --env-file .env.runtime -f docker-compose.enterprise.yml up -d"
  fi

  run_remote_phase "show remote compose status" sc_transport_run "cd '${REMOTE_DIR}/rendered' && docker compose --env-file .env.runtime -f docker-compose.enterprise.yml ps"
  run_remote_phase "show remote application images" show_remote_application_images
fi

info "remote enterprise deployment flow completed"
