#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
TRANSPORT_LIB="${SCRIPT_DIR}/lib/transport.sh"
DEFAULT_ENV_FILE="${SCRIPT_DIR}/enterprise.env"
DEFAULT_REMOTE_DIR="/opt/security-center/enterprise"
DEFAULT_KEY_DIR="${SCRIPT_DIR}/.state/keys"

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

usage() {
  cat <<'EOF'
Clean up Security Center enterprise sandbox resources.

This helper deletes the AWS resources provisioned by scripts/enterprise and can
also remove the remote deployment bundle plus Let's Encrypt certificate files on
the EC2 host.

Safety model:
  - without --force: print the cleanup plan and exit without deleting anything
  - with --force: prompt for confirmation unless --yes is also provided

Usage:
  ./scripts/enterprise/cleanup-enterprise.sh \
    --env-file ./scripts/enterprise/enterprise.env

Options:
  --env-file <path>        Enterprise env file. Default: scripts/enterprise/enterprise.env
  --transport <mode>       Optional. ssm (default) or ssh.
  --instance-id <id>       Optional override for ssm.
  --host <user@host>       Optional override for ssh.
  --identity-file <path>   Optional SSH private key for ssh mode.
  --remote-dir <path>      Optional. Default: /opt/security-center/enterprise
  --region <name>          AWS region override.
  --profile <name>         AWS CLI profile override.
  --force                  Execute deletions after validation.
  --yes                    Skip the interactive confirmation prompt. Requires --force.
  --dry-run                Print planned commands without deleting resources.
  --keep-certs             Keep Let's Encrypt certificate files on the remote host.
  --revoke-certs           Revoke the current Let's Encrypt certificate before removing local files.
  --keep-buckets           Keep artifact/uploads buckets.
  --keep-ssm               Keep SSM parameters under the configured prefix.
  --help                   Show this help.
EOF
}

info() { echo "INFO: $*"; }
warn() { echo "WARN: $*" >&2; }
die() { echo "ERROR: $*" >&2; exit 1; }

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_cleanup_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}"))"
}

trim() {
  local value="${1:-}"
  value="${value#"${value%%[![:space:]]*}"}"
  value="${value%"${value##*[![:space:]]}"}"
  printf '%s' "$value"
}

resolve_path() {
  local path="$1"
  if [[ -z "${path}" ]]; then
    printf '%s' ""
    return 0
  fi
  if [[ "${path}" = /* ]]; then
    printf '%s' "${path}"
    return 0
  fi
  if [[ -e "${path}" ]]; then
    printf '%s' "${path}"
    return 0
  fi
  if [[ -e "${ROOT_DIR}/${path}" ]]; then
    printf '%s' "${ROOT_DIR}/${path}"
    return 0
  fi
  printf '%s' "${path}"
}

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

discover_tagged_buckets() {
  [[ "${KEEP_BUCKETS}" == "true" ]] && return 0
  local bucket_name=""
  local tag_payload=""
  local bucket_region=""
  while IFS= read -r bucket_name; do
    [[ -n "${bucket_name}" ]] || continue
    case "${bucket_name}" in
      "${SC_S3_ARTIFACT_BUCKET}"|"${SC_UPLOADS_BUCKET}")
        continue
        ;;
    esac
    tag_payload="$(aws s3api get-bucket-tagging --bucket "${bucket_name}" --output json 2>/dev/null || true)"
    [[ -n "${tag_payload}" ]] || continue
    if ! python3 - "${tag_payload}" "${SC_EC2_NAME_PREFIX}" <<'PY' >/dev/null
import json
import sys

doc = json.loads(sys.argv[1])
stack_id = sys.argv[2]
tags = {item["Key"]: item["Value"] for item in doc.get("TagSet", [])}
if tags.get("ManagedBy") != "scripts/enterprise":
    raise SystemExit(1)
if tags.get("Project") != "security-center":
    raise SystemExit(1)
if tags.get("StackId") != stack_id:
    raise SystemExit(1)
PY
    then
      continue
    fi
    bucket_region="$(aws s3api get-bucket-location --bucket "${bucket_name}" --query 'LocationConstraint' --output text 2>/dev/null || true)"
    if [[ -z "${bucket_region}" || "${bucket_region}" == "None" ]]; then
      bucket_region="us-east-1"
    fi
    DISCOVERED_BUCKETS+=("${bucket_name}|${bucket_region}")
  done < <(aws s3api list-buckets --query 'Buckets[].Name' --output text 2>/dev/null | tr '\t' '\n')
}

get_env_value() {
  local env_file="$1"
  local key="$2"
  [[ -f "${env_file}" ]] || return 0
  python3 - "$env_file" "$key" <<'PY'
from pathlib import Path
import sys

path = Path(sys.argv[1])
key = sys.argv[2]
for raw in path.read_text().splitlines():
    line = raw.strip()
    if not line or line.startswith("#") or "=" not in line:
        continue
    lhs, rhs = line.split("=", 1)
    if lhs.strip() == key:
        print(rhs.strip())
        break
PY
}

set_env_value() {
  local env_file="$1"
  local key="$2"
  local value="$3"
  python3 - "$env_file" "$key" "$value" <<'PY'
from pathlib import Path
import sys

path = Path(sys.argv[1])
key = sys.argv[2]
value = sys.argv[3]
lines = path.read_text().splitlines() if path.exists() else []
found = False
out = []
for raw in lines:
    if raw.strip().startswith(f"{key}="):
        out.append(f"{key}={value}")
        found = True
    else:
        out.append(raw)
if not found:
    if out and out[-1].strip():
        out.append("")
    out.append(f"{key}={value}")
path.write_text("\n".join(out) + "\n")
PY
}

is_truthy() {
  case "$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')" in
    true|1|yes|y|on)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

record_success() {
  RESULTS_SUCCESS+=("$1")
}

record_skip() {
  RESULTS_SKIPPED+=("$1")
}

record_failure() {
  RESULTS_FAILED+=("$1")
}

aws_cli() {
  if [[ "${DRY_RUN}" == "true" ]]; then
    echo "DRY-RUN aws $*"
    return 0
  fi
  aws "$@"
}

ssm_target_online() {
  [[ -n "${SC_EC2_INSTANCE_ID}" ]] || return 1
  local ping_status=""
  ping_status="$(aws ssm describe-instance-information \
    --region "${SC_AWS_REGION_EFFECTIVE}" \
    --filters "Key=InstanceIds,Values=${SC_EC2_INSTANCE_ID}" \
    --query 'InstanceInformationList[0].PingStatus' \
    --output text 2>/dev/null || true)"
  [[ "${ping_status}" == "Online" ]]
}

remote_run() {
  local description="$1"
  local command="$2"
  if [[ "${REMOTE_AVAILABLE}" != "true" ]]; then
    record_skip "${description}: remote target unavailable"
    return 0
  fi
  if [[ "${DRY_RUN}" == "true" ]]; then
    sc_transport_run "${command}" >/dev/null
    record_success "${description} (dry-run)"
    return 0
  fi
  if sc_transport_run "${command}" >/dev/null; then
    record_success "${description}"
  else
    if [[ "${TRANSPORT}" == "ssm" ]] && ! instance_exists; then
      REMOTE_AVAILABLE="false"
      info "${description}: remote target ${SC_EC2_INSTANCE_ID} is no longer available; skipping remaining remote cleanup work"
      record_skip "${description}: remote target no longer available"
      return 0
    fi
    warn "${description} failed"
    record_failure "${description}"
  fi
}

remote_cleanup() {
  local cert_live cert_archive cert_renewal cert_cert cert_privkey
  cert_live="/etc/letsencrypt/live/${SC_PUBLIC_HOSTNAME}"
  cert_archive="/etc/letsencrypt/archive/${SC_PUBLIC_HOSTNAME}"
  cert_renewal="/etc/letsencrypt/renewal/${SC_PUBLIC_HOSTNAME}.conf"
  cert_cert="${cert_live}/cert.pem"
  cert_privkey="${cert_live}/privkey.pem"

  remote_run "stop remote compose stack" "bash -lc 'if [[ -f \"${REMOTE_DIR}/rendered/docker-compose.enterprise.yml\" && -f \"${REMOTE_DIR}/rendered/.env.runtime\" ]]; then cd \"${REMOTE_DIR}/rendered\" && docker compose --env-file .env.runtime -f docker-compose.enterprise.yml down -v >/dev/null 2>&1 || true; fi'"
  remote_run "remove remote deployment directory" "sudo rm -rf '${REMOTE_DIR}'"

  if [[ "${KEEP_CERTS}" == "true" ]]; then
    record_skip "remove Let's Encrypt certificate files: disabled by --keep-certs"
    return 0
  fi

  if [[ "${REVOKE_CERTS}" == "true" ]]; then
    remote_run "revoke Let's Encrypt certificate" "bash -lc 'if [[ -f \"${cert_cert}\" && -f \"${cert_privkey}\" ]]; then sudo certbot revoke --non-interactive --cert-path \"${cert_cert}\" --key-path \"${cert_privkey}\" --reason cessation; else echo \"certificate files missing; skip revoke\"; fi'"
  fi

  remote_run "remove Let's Encrypt certificate files" "sudo rm -rf '${cert_live}' '${cert_archive}' '${cert_renewal}'"
}

load_env_defaults() {
  ENV_FILE="$(resolve_path "${ENV_FILE}")"
  [[ -f "${ENV_FILE}" ]] || die "env file not found: ${ENV_FILE}"

  SC_PUBLIC_HOSTNAME="$(trim "$(get_env_value "${ENV_FILE}" "SC_PUBLIC_HOSTNAME")")"
  SC_AWS_REGION_VALUE="$(trim "$(get_env_value "${ENV_FILE}" "SC_AWS_REGION")")"
  SC_EC2_INSTANCE_ID="${INSTANCE_ID:-$(trim "$(get_env_value "${ENV_FILE}" "SC_EC2_INSTANCE_ID")")}"
  SC_EC2_PUBLIC_IP="$(trim "$(get_env_value "${ENV_FILE}" "SC_EC2_PUBLIC_IP")")"
  SC_EC2_SECURITY_GROUP_ID="$(trim "$(get_env_value "${ENV_FILE}" "SC_EC2_SECURITY_GROUP_ID")")"
  SC_EC2_EIP_ALLOCATION_ID="$(trim "$(get_env_value "${ENV_FILE}" "SC_EC2_EIP_ALLOCATION_ID")")"
  SC_EC2_INSTANCE_PROFILE_NAME="$(trim "$(get_env_value "${ENV_FILE}" "SC_EC2_INSTANCE_PROFILE_NAME")")"
  SC_EC2_IAM_ROLE_NAME="$(trim "$(get_env_value "${ENV_FILE}" "SC_EC2_IAM_ROLE_NAME")")"
  SC_EC2_NAME_PREFIX="$(trim "$(get_env_value "${ENV_FILE}" "SC_EC2_NAME_PREFIX")")"
  SC_EC2_KEY_PAIR_NAME="$(trim "$(get_env_value "${ENV_FILE}" "SC_EC2_KEY_PAIR_NAME")")"
  SC_EC2_IDENTITY_FILE="$(resolve_path "$(trim "$(get_env_value "${ENV_FILE}" "SC_EC2_IDENTITY_FILE")")")"
  SC_AWS_REGION_EFFECTIVE="${AWS_REGION_NAME:-${SC_AWS_REGION_VALUE}}"
  SC_S3_ARTIFACT_BUCKET="$(trim "$(get_env_value "${ENV_FILE}" "SC_S3_ARTIFACT_BUCKET")")"
  SC_UPLOADS_ENABLED="$(trim "$(get_env_value "${ENV_FILE}" "SC_UPLOADS_ENABLED")")"
  SC_UPLOADS_BUCKET="$(trim "$(get_env_value "${ENV_FILE}" "SC_UPLOADS_BUCKET")")"
  SC_UPLOADS_REGION="$(trim "$(get_env_value "${ENV_FILE}" "SC_UPLOADS_REGION")")"
  SC_SSM_PATH_PREFIX="$(trim "$(get_env_value "${ENV_FILE}" "SC_SSM_PATH_PREFIX")")"
  SC_SANDBOX_AUTH_MODE="$(trim "$(get_env_value "${ENV_FILE}" "SC_SANDBOX_AUTH_MODE")")"
  SC_REMOTE_TRANSPORT="$(trim "$(get_env_value "${ENV_FILE}" "SC_REMOTE_TRANSPORT")")"
  SC_EC2_SSH_USER="$(trim "$(get_env_value "${ENV_FILE}" "SC_EC2_SSH_USER")")"

  [[ -n "${SC_PUBLIC_HOSTNAME}" ]] || die "SC_PUBLIC_HOSTNAME is required in ${ENV_FILE}"
  [[ -n "${SC_AWS_REGION_EFFECTIVE}" ]] || die "AWS region is required via --region or SC_AWS_REGION"
  [[ -n "${SC_SSM_PATH_PREFIX}" ]] || die "SC_SSM_PATH_PREFIX is required in ${ENV_FILE}"

  if [[ -z "${TRANSPORT}" || "${TRANSPORT}" == "ssm" ]]; then
    TRANSPORT="${SC_REMOTE_TRANSPORT:-ssm}"
  fi
  [[ "${TRANSPORT}" == "ssm" || "${TRANSPORT}" == "ssh" ]] || die "unsupported transport: ${TRANSPORT}"

  if [[ "${TRANSPORT}" == "ssh" && -z "${REMOTE_HOST}" && -n "${SC_EC2_PUBLIC_IP}" ]]; then
    REMOTE_HOST="${SC_EC2_SSH_USER:-ec2-user}@${SC_EC2_PUBLIC_IP}"
  fi
  if [[ -z "${IDENTITY_FILE}" && -n "${SC_EC2_IDENTITY_FILE}" ]]; then
    IDENTITY_FILE="${SC_EC2_IDENTITY_FILE}"
  fi
  if [[ -z "${SC_UPLOADS_REGION}" ]]; then
    SC_UPLOADS_REGION="${SC_AWS_REGION_EFFECTIVE}"
  fi
}

ensure_aws_context() {
  require_bin aws
  require_bin python3
  export AWS_REGION="${SC_AWS_REGION_EFFECTIVE}"
  export AWS_DEFAULT_REGION="${SC_AWS_REGION_EFFECTIVE}"
  export AWS_SDK_LOAD_CONFIG=1
  if [[ -n "${AWS_PROFILE_NAME}" ]]; then
    export AWS_PROFILE="${AWS_PROFILE_NAME}"
  fi
  if [[ "${DRY_RUN}" != "true" ]] && ! aws sts get-caller-identity >/dev/null 2>&1; then
    die "AWS session is not active; run aws sso login first"
  fi
  ACCOUNT_ID="$(aws sts get-caller-identity --query 'Account' --output text 2>/dev/null || true)"
}

instance_exists() {
  [[ -n "${SC_EC2_INSTANCE_ID}" ]] || return 1
  local state=""
  state="$(aws ec2 describe-instances \
    --region "${SC_AWS_REGION_EFFECTIVE}" \
    --instance-ids "${SC_EC2_INSTANCE_ID}" \
    --query 'Reservations[0].Instances[0].State.Name' \
    --output text 2>/dev/null || true)"
  case "${state}" in
    ""|None|terminated|shutting-down)
      return 1
      ;;
    *)
      return 0
      ;;
  esac
}

init_transport_if_possible() {
  REMOTE_AVAILABLE="false"
  REMOTE_UNAVAILABLE_REASON="not initialized"
  case "${TRANSPORT}" in
    ssm)
      if [[ -z "${SC_EC2_INSTANCE_ID}" ]]; then
        REMOTE_UNAVAILABLE_REASON="instance id missing"
        return 0
      fi
      if [[ "${DRY_RUN}" == "true" ]]; then
        REMOTE_UNAVAILABLE_REASON="dry-run"
        SC_TRANSPORT_MODE="ssm"
        SC_TRANSPORT_DRY_RUN="${DRY_RUN}"
        SC_TRANSPORT_INSTANCE_ID="${SC_EC2_INSTANCE_ID}"
        SC_TRANSPORT_AWS_REGION="${SC_AWS_REGION_EFFECTIVE}"
        SC_TRANSPORT_AWS_PROFILE="${AWS_PROFILE_NAME}"
        sc_transport_init
        REMOTE_AVAILABLE="true"
        return 0
      fi
      if ! instance_exists; then
        REMOTE_UNAVAILABLE_REASON="instance absent"
        return 0
      fi
      if ! ssm_target_online; then
        REMOTE_UNAVAILABLE_REASON="ssm target unavailable"
        return 0
      fi
      SC_TRANSPORT_MODE="ssm"
      SC_TRANSPORT_DRY_RUN="${DRY_RUN}"
      SC_TRANSPORT_INSTANCE_ID="${SC_EC2_INSTANCE_ID}"
      SC_TRANSPORT_AWS_REGION="${SC_AWS_REGION_EFFECTIVE}"
      SC_TRANSPORT_AWS_PROFILE="${AWS_PROFILE_NAME}"
      sc_transport_init
      REMOTE_AVAILABLE="true"
      REMOTE_UNAVAILABLE_REASON=""
      ;;
    ssh)
      if [[ -n "${REMOTE_HOST}" ]]; then
        SC_TRANSPORT_MODE="ssh"
        SC_TRANSPORT_DRY_RUN="${DRY_RUN}"
        SC_TRANSPORT_REMOTE_HOST="${REMOTE_HOST}"
        SC_TRANSPORT_IDENTITY_FILE="${IDENTITY_FILE}"
        sc_transport_init
        REMOTE_AVAILABLE="true"
        REMOTE_UNAVAILABLE_REASON=""
      else
        REMOTE_UNAVAILABLE_REASON="ssh target missing"
      fi
      ;;
  esac
}

confirm_execution() {
  [[ "${FORCE}" == "true" ]] || return 0
  if [[ "${YES}" == "true" ]]; then
    return 0
  fi
  printf "Type DELETE to destroy the enterprise sandbox resources for %s: " "${SC_PUBLIC_HOSTNAME}"
  local answer=""
  IFS= read -r answer || true
  [[ "${answer}" == "DELETE" ]] || die "cleanup cancelled"
}

print_summary() {
  info "cleanup plan summary:"
  info "  - env file: ${ENV_FILE}"
  info "  - aws account: ${ACCOUNT_ID:-<unresolved>}"
  info "  - aws region: ${SC_AWS_REGION_EFFECTIVE}"
  info "  - hostname: ${SC_PUBLIC_HOSTNAME}"
  info "  - transport: ${TRANSPORT}"
  if [[ -n "${SC_EC2_INSTANCE_ID}" ]]; then
    info "  - instance id: ${SC_EC2_INSTANCE_ID}"
  fi
  if [[ -n "${SC_EC2_EIP_ALLOCATION_ID}" ]]; then
    info "  - eip allocation id: ${SC_EC2_EIP_ALLOCATION_ID}"
  fi
  if [[ -n "${SC_EC2_SECURITY_GROUP_ID}" ]]; then
    info "  - security group id: ${SC_EC2_SECURITY_GROUP_ID}"
  fi
  if [[ -n "${SC_EC2_INSTANCE_PROFILE_NAME}" ]]; then
    info "  - instance profile: ${SC_EC2_INSTANCE_PROFILE_NAME}"
  fi
  if [[ -n "${SC_EC2_IAM_ROLE_NAME}" ]]; then
    info "  - iam role: ${SC_EC2_IAM_ROLE_NAME}"
  fi
  if [[ -n "${SC_S3_ARTIFACT_BUCKET}" ]]; then
    info "  - artifact bucket: ${SC_S3_ARTIFACT_BUCKET}"
  fi
  if is_truthy "${SC_UPLOADS_ENABLED}" && [[ -n "${SC_UPLOADS_BUCKET}" ]]; then
    info "  - uploads bucket: ${SC_UPLOADS_BUCKET} (${SC_UPLOADS_REGION})"
  fi
  local discovered=""
  for discovered in "${DISCOVERED_BUCKETS[@]-}"; do
    [[ -n "${discovered}" ]] || continue
    info "  - discovered tagged bucket: ${discovered%%|*} (${discovered##*|})"
  done
  info "  - ssm path prefix: ${SC_SSM_PATH_PREFIX}"
  info "  - remote dir: ${REMOTE_DIR}"
  if [[ "${REMOTE_AVAILABLE}" == "true" ]]; then
    info "  - remote target: available via ${TRANSPORT}"
  else
    info "  - remote target: unavailable (${REMOTE_UNAVAILABLE_REASON}); remote dir/certificate cleanup will be skipped"
  fi
  if [[ "${KEEP_CERTS}" == "true" ]]; then
    info "  - certificates: keep"
  else
    info "  - certificates: delete /etc/letsencrypt/{live,archive,renewal}/${SC_PUBLIC_HOSTNAME}"
    if [[ "${REVOKE_CERTS}" == "true" ]]; then
      info "  - certificate revocation: enabled via certbot revoke"
    fi
  fi
  if [[ -n "${SC_EC2_IDENTITY_FILE}" ]]; then
    info "  - local identity file: ${SC_EC2_IDENTITY_FILE}"
  fi
  if [[ "${KEEP_BUCKETS}" == "true" ]]; then
    info "  - buckets: keep"
  fi
  if [[ "${KEEP_SSM}" == "true" ]]; then
    info "  - ssm parameters: keep"
  fi
}

delete_instance() {
  if [[ -z "${SC_EC2_INSTANCE_ID}" ]]; then
    record_skip "terminate EC2 instance: no instance id in env"
    return 0
  fi
  if [[ "${DRY_RUN}" == "true" ]]; then
    aws_cli ec2 terminate-instances --region "${SC_AWS_REGION_EFFECTIVE}" --instance-ids "${SC_EC2_INSTANCE_ID}" >/dev/null
    record_success "terminate EC2 instance (dry-run)"
    return 0
  fi
  if ! instance_exists; then
    record_skip "terminate EC2 instance: already absent"
    return 0
  fi
  aws ec2 terminate-instances --region "${SC_AWS_REGION_EFFECTIVE}" --instance-ids "${SC_EC2_INSTANCE_ID}" >/dev/null
  aws ec2 wait instance-terminated --region "${SC_AWS_REGION_EFFECTIVE}" --instance-ids "${SC_EC2_INSTANCE_ID}"
  record_success "terminate EC2 instance"
}

delete_eip() {
  if [[ -z "${SC_EC2_EIP_ALLOCATION_ID}" ]]; then
    record_skip "release Elastic IP: no allocation id in env"
    return 0
  fi
  if [[ "${DRY_RUN}" == "true" ]]; then
    aws_cli ec2 release-address --region "${SC_AWS_REGION_EFFECTIVE}" --allocation-id "${SC_EC2_EIP_ALLOCATION_ID}" >/dev/null
    record_success "release Elastic IP (dry-run)"
    return 0
  fi
  if ! aws ec2 describe-addresses --region "${SC_AWS_REGION_EFFECTIVE}" --allocation-ids "${SC_EC2_EIP_ALLOCATION_ID}" >/dev/null 2>&1; then
    record_skip "release Elastic IP: already absent"
    return 0
  fi
  aws ec2 release-address --region "${SC_AWS_REGION_EFFECTIVE}" --allocation-id "${SC_EC2_EIP_ALLOCATION_ID}" >/dev/null
  record_success "release Elastic IP"
}

delete_key_pair() {
  if [[ -z "${SC_EC2_KEY_PAIR_NAME}" ]]; then
    record_skip "delete EC2 key pair: not configured"
    return 0
  fi
  if [[ "${DRY_RUN}" == "true" ]]; then
    aws_cli ec2 delete-key-pair --region "${SC_AWS_REGION_EFFECTIVE}" --key-name "${SC_EC2_KEY_PAIR_NAME}" >/dev/null
    record_success "delete EC2 key pair (dry-run)"
  else
    if aws ec2 describe-key-pairs --region "${SC_AWS_REGION_EFFECTIVE}" --key-names "${SC_EC2_KEY_PAIR_NAME}" >/dev/null 2>&1; then
      aws ec2 delete-key-pair --region "${SC_AWS_REGION_EFFECTIVE}" --key-name "${SC_EC2_KEY_PAIR_NAME}" >/dev/null
      record_success "delete EC2 key pair"
    else
      record_skip "delete EC2 key pair: already absent"
    fi
  fi

  if [[ -n "${SC_EC2_IDENTITY_FILE}" && -f "${SC_EC2_IDENTITY_FILE}" ]]; then
    case "${SC_EC2_IDENTITY_FILE}" in
      "${DEFAULT_KEY_DIR}"/*)
        if [[ "${DRY_RUN}" == "true" ]]; then
          record_success "remove local identity file (dry-run)"
        else
          rm -f "${SC_EC2_IDENTITY_FILE}"
          record_success "remove local identity file"
        fi
        ;;
      *)
        record_skip "remove local identity file: outside ${DEFAULT_KEY_DIR}"
        ;;
    esac
  fi
}

delete_security_group() {
  if [[ -z "${SC_EC2_SECURITY_GROUP_ID}" ]]; then
    record_skip "delete security group: no security group id in env"
    return 0
  fi
  if [[ "${DRY_RUN}" == "true" ]]; then
    aws_cli ec2 delete-security-group --region "${SC_AWS_REGION_EFFECTIVE}" --group-id "${SC_EC2_SECURITY_GROUP_ID}" >/dev/null
    record_success "delete security group (dry-run)"
    return 0
  fi
  if ! aws ec2 describe-security-groups --region "${SC_AWS_REGION_EFFECTIVE}" --group-ids "${SC_EC2_SECURITY_GROUP_ID}" >/dev/null 2>&1; then
    record_skip "delete security group: already absent"
    return 0
  fi
  aws ec2 delete-security-group --region "${SC_AWS_REGION_EFFECTIVE}" --group-id "${SC_EC2_SECURITY_GROUP_ID}" >/dev/null
  record_success "delete security group"
}

delete_bucket_versions_if_needed() {
  local bucket="$1"
  local region="$2"
  local payload=""
  payload="$(aws s3api list-object-versions --bucket "${bucket}" --region "${region}" --output json 2>/dev/null || true)"
  [[ -n "${payload}" ]] || return 0
  python3 - "${payload}" <<'PY' | while IFS=$'\t' read -r key version_id; do
import json
import sys

doc = json.loads(sys.argv[1])
for section in ("Versions", "DeleteMarkers"):
    for item in doc.get(section, []):
        key = item.get("Key")
        version = item.get("VersionId")
        if key and version:
            print(f"{key}\t{version}")
PY
    [[ -n "${key}" && -n "${version_id}" ]] || continue
    aws s3api delete-object --bucket "${bucket}" --key "${key}" --version-id "${version_id}" --region "${region}" >/dev/null
  done
}

delete_bucket() {
  local bucket="$1"
  local region="$2"
  local label="$3"

  if [[ -z "${bucket}" ]]; then
    record_skip "delete ${label}: bucket not configured"
    return 0
  fi
  if [[ "${KEEP_BUCKETS}" == "true" ]]; then
    record_skip "delete ${label}: disabled by --keep-buckets"
    return 0
  fi
  if [[ "${DRY_RUN}" == "true" ]]; then
    aws_cli s3 rm "s3://${bucket}" --recursive --region "${region}" >/dev/null
    aws_cli s3api delete-bucket --bucket "${bucket}" --region "${region}" >/dev/null
    record_success "delete ${label} (dry-run)"
    return 0
  fi
  if ! aws s3api head-bucket --bucket "${bucket}" >/dev/null 2>&1; then
    record_skip "delete ${label}: already absent"
    return 0
  fi
  aws s3 rm "s3://${bucket}" --recursive --region "${region}" >/dev/null 2>&1 || true
  delete_bucket_versions_if_needed "${bucket}" "${region}" || true
  aws s3api delete-bucket --bucket "${bucket}" --region "${region}" >/dev/null
  record_success "delete ${label}"
}

delete_all_inline_policies_for_role() {
  local role_name="$1"
  local policy_names=()
  local policy_name=""

  if [[ -z "${role_name}" ]]; then
    record_skip "delete IAM inline policies: role not configured"
    return 0
  fi
  if ! aws iam get-role --role-name "${role_name}" >/dev/null 2>&1; then
    record_skip "delete IAM inline policies: role already absent"
    return 0
  fi

  read -r -a policy_names <<<"$(aws iam list-role-policies \
    --role-name "${role_name}" \
    --query 'PolicyNames[]' \
    --output text 2>/dev/null || true)"

  if (( ${#policy_names[@]} == 0 )); then
    record_skip "delete IAM inline policies: none found"
    return 0
  fi

  for policy_name in "${policy_names[@]}"; do
    [[ -n "${policy_name}" ]] || continue
    if [[ "${DRY_RUN}" == "true" ]]; then
      aws_cli iam delete-role-policy --role-name "${role_name}" --policy-name "${policy_name}" >/dev/null
      record_success "delete IAM inline policy ${policy_name} (dry-run)"
    else
      aws iam delete-role-policy --role-name "${role_name}" --policy-name "${policy_name}" >/dev/null
      record_success "delete IAM inline policy ${policy_name}"
    fi
  done
}

detach_all_managed_policies_for_role() {
  local role_name="$1"
  local policy_arns=()
  local policy_arn=""

  if [[ -z "${role_name}" ]]; then
    record_skip "detach IAM managed policies: role not configured"
    return 0
  fi
  if ! aws iam get-role --role-name "${role_name}" >/dev/null 2>&1; then
    record_skip "detach IAM managed policies: role already absent"
    return 0
  fi

  read -r -a policy_arns <<<"$(aws iam list-attached-role-policies \
    --role-name "${role_name}" \
    --query 'AttachedPolicies[].PolicyArn' \
    --output text 2>/dev/null || true)"

  if (( ${#policy_arns[@]} == 0 )); then
    record_skip "detach IAM managed policies: none found"
    return 0
  fi

  for policy_arn in "${policy_arns[@]}"; do
    [[ -n "${policy_arn}" ]] || continue
    if [[ "${DRY_RUN}" == "true" ]]; then
      aws_cli iam detach-role-policy --role-name "${role_name}" --policy-arn "${policy_arn}" >/dev/null
      record_success "detach IAM managed policy ${policy_arn} (dry-run)"
    else
      aws iam detach-role-policy --role-name "${role_name}" --policy-arn "${policy_arn}" >/dev/null
      record_success "detach IAM managed policy ${policy_arn}"
    fi
  done
}

delete_iam_role_and_profile() {
  if [[ -z "${SC_EC2_IAM_ROLE_NAME}" ]]; then
    record_skip "delete IAM role/profile: role not configured"
    return 0
  fi

  delete_all_inline_policies_for_role "${SC_EC2_IAM_ROLE_NAME}"

  if [[ -n "${SC_EC2_INSTANCE_PROFILE_NAME}" ]]; then
    if [[ "${DRY_RUN}" == "true" ]]; then
      aws_cli iam remove-role-from-instance-profile --instance-profile-name "${SC_EC2_INSTANCE_PROFILE_NAME}" --role-name "${SC_EC2_IAM_ROLE_NAME}" >/dev/null
      aws_cli iam delete-instance-profile --instance-profile-name "${SC_EC2_INSTANCE_PROFILE_NAME}" >/dev/null
      record_success "delete instance profile (dry-run)"
    else
      if aws iam get-instance-profile --instance-profile-name "${SC_EC2_INSTANCE_PROFILE_NAME}" >/dev/null 2>&1; then
        aws iam remove-role-from-instance-profile --instance-profile-name "${SC_EC2_INSTANCE_PROFILE_NAME}" --role-name "${SC_EC2_IAM_ROLE_NAME}" >/dev/null 2>&1 || true
        aws iam delete-instance-profile --instance-profile-name "${SC_EC2_INSTANCE_PROFILE_NAME}" >/dev/null
        record_success "delete instance profile"
      else
        record_skip "delete instance profile: already absent"
      fi
    fi
  fi

  if [[ "${DRY_RUN}" == "true" ]]; then
    detach_all_managed_policies_for_role "${SC_EC2_IAM_ROLE_NAME}"
    aws_cli iam delete-role --role-name "${SC_EC2_IAM_ROLE_NAME}" >/dev/null
    record_success "delete IAM role (dry-run)"
    return 0
  fi

  if ! aws iam get-role --role-name "${SC_EC2_IAM_ROLE_NAME}" >/dev/null 2>&1; then
    record_skip "delete IAM role: already absent"
    return 0
  fi
  detach_all_managed_policies_for_role "${SC_EC2_IAM_ROLE_NAME}"
  aws iam delete-role --role-name "${SC_EC2_IAM_ROLE_NAME}" >/dev/null
  record_success "delete IAM role"
}

delete_ssm_parameter() {
  local name="$1"
  local label="$2"
  if [[ "${KEEP_SSM}" == "true" ]]; then
    record_skip "delete ${label}: disabled by --keep-ssm"
    return 0
  fi
  if [[ "${DRY_RUN}" == "true" ]]; then
    aws_cli ssm delete-parameter --region "${SC_AWS_REGION_EFFECTIVE}" --name "${name}" >/dev/null
    record_success "delete ${label} (dry-run)"
    return 0
  fi
  if ! aws ssm get-parameter --name "${name}" --region "${SC_AWS_REGION_EFFECTIVE}" >/dev/null 2>&1; then
    record_skip "delete ${label}: already absent"
    return 0
  fi
  aws ssm delete-parameter --region "${SC_AWS_REGION_EFFECTIVE}" --name "${name}" >/dev/null
  record_success "delete ${label}"
}

delete_ssm_parameters() {
  local prefix="${SC_SSM_PATH_PREFIX%/}"
  delete_ssm_parameter "${prefix}/mysql/root_password" "SSM mysql root password"
  delete_ssm_parameter "${prefix}/mysql/app_password" "SSM mysql app password"
  delete_ssm_parameter "${prefix}/redis/password" "SSM redis password"
  delete_ssm_parameter "${prefix}/jwt/access_secret" "SSM JWT access secret"
  delete_ssm_parameter "${prefix}/jwt/refresh_secret" "SSM JWT refresh secret"
  if is_truthy "${SC_SANDBOX_AUTH_MODE:-false}"; then
    delete_ssm_parameter "${prefix}/auth0/client_secret" "SSM Auth0 client secret placeholder"
    delete_ssm_parameter "${prefix}/feishu/org_sync/app_id" "SSM Feishu org app id placeholder"
    delete_ssm_parameter "${prefix}/feishu/org_sync/app_secret" "SSM Feishu org app secret placeholder"
    delete_ssm_parameter "${prefix}/feishu/alerts/app_id" "SSM Feishu alerts app id placeholder"
    delete_ssm_parameter "${prefix}/feishu/alerts/app_secret" "SSM Feishu alerts app secret placeholder"
  fi
}

delete_discovered_buckets() {
  local item=""
  local bucket=""
  local region=""
  for item in "${DISCOVERED_BUCKETS[@]-}"; do
    [[ -n "${item}" ]] || continue
    bucket="${item%%|*}"
    region="${item##*|}"
    delete_bucket "${bucket}" "${region}" "discovered tagged bucket ${bucket}"
  done
}

clear_stale_env_state() {
  [[ "${DRY_RUN}" == "true" ]] && return 0
  [[ -f "${ENV_FILE}" ]] || return 0

  set_env_value "${ENV_FILE}" "SC_UPLOADS_BUCKET" ""
  set_env_value "${ENV_FILE}" "SC_S3_ARTIFACT_BUCKET" ""
  set_env_value "${ENV_FILE}" "SC_S3_ARTIFACT_PREFIX" ""
  set_env_value "${ENV_FILE}" "SC_EC2_INSTANCE_ID" ""
  set_env_value "${ENV_FILE}" "SC_EC2_PUBLIC_IP" ""
  set_env_value "${ENV_FILE}" "SC_EC2_PUBLIC_DNS" ""
  set_env_value "${ENV_FILE}" "SC_EC2_SECURITY_GROUP_ID" ""
  set_env_value "${ENV_FILE}" "SC_EC2_EIP_ALLOCATION_ID" ""
  set_env_value "${ENV_FILE}" "SC_EC2_INSTANCE_PROFILE_NAME" ""
  set_env_value "${ENV_FILE}" "SC_EC2_IAM_ROLE_NAME" ""
  set_env_value "${ENV_FILE}" "SC_EC2_AMI_ID" ""
  info "cleared stale EC2/EIP/IAM/S3 resource fields from ${ENV_FILE}"
}

print_results() {
  echo
  info "cleanup results:"
  local item=""
  local failed_count=0
  for item in "${RESULTS_SUCCESS[@]-}"; do
    [[ -n "${item}" ]] || continue
    info "  - success: ${item}"
  done
  for item in "${RESULTS_SKIPPED[@]-}"; do
    [[ -n "${item}" ]] || continue
    info "  - skipped: ${item}"
  done
  for item in "${RESULTS_FAILED[@]-}"; do
    [[ -n "${item}" ]] || continue
    failed_count=$((failed_count + 1))
    warn "  - failed: ${item}"
  done
  if ((failed_count > 0)); then
    return 1
  fi
  return 0
}

ENV_FILE="${DEFAULT_ENV_FILE}"
TRANSPORT="ssm"
INSTANCE_ID=""
REMOTE_HOST=""
IDENTITY_FILE=""
REMOTE_DIR="${DEFAULT_REMOTE_DIR}"
AWS_REGION_NAME="${AWS_REGION:-${AWS_DEFAULT_REGION:-}}"
AWS_PROFILE_NAME="${AWS_PROFILE:-}"
FORCE="false"
YES="false"
DRY_RUN="false"
KEEP_CERTS="false"
REVOKE_CERTS="false"
KEEP_BUCKETS="false"
KEEP_SSM="false"
REMOTE_AVAILABLE="false"
REMOTE_UNAVAILABLE_REASON="not initialized"
RESULTS_SUCCESS=()
RESULTS_SKIPPED=()
RESULTS_FAILED=()
DISCOVERED_BUCKETS=()

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
      ;;
    --identity-file)
      IDENTITY_FILE="$(resolve_path "$2")"
      shift 2
      ;;
    --remote-dir)
      REMOTE_DIR="$2"
      shift 2
      ;;
    --region)
      AWS_REGION_NAME="$2"
      shift 2
      ;;
    --profile)
      AWS_PROFILE_NAME="$2"
      shift 2
      ;;
    --force)
      FORCE="true"
      shift
      ;;
    --yes)
      YES="true"
      shift
      ;;
    --dry-run)
      DRY_RUN="true"
      shift
      ;;
    --keep-certs)
      KEEP_CERTS="true"
      shift
      ;;
    --revoke-certs)
      REVOKE_CERTS="true"
      shift
      ;;
    --keep-buckets)
      KEEP_BUCKETS="true"
      shift
      ;;
    --keep-ssm)
      KEEP_SSM="true"
      shift
      ;;
    --help|-h)
      usage
      exit 0
      ;;
    *)
      die "unknown argument: $1"
      ;;
  esac
done

[[ "${YES}" == "true" && "${FORCE}" != "true" ]] && die "--yes requires --force"
[[ "${KEEP_CERTS}" == "true" && "${REVOKE_CERTS}" == "true" ]] && die "--keep-certs and --revoke-certs cannot be used together"

run_cleanup_phase "load cleanup env defaults" load_env_defaults
run_cleanup_phase "resolve AWS context" ensure_aws_context
run_cleanup_phase "discover tagged buckets" discover_tagged_buckets
if [[ -n "${IDENTITY_FILE}" ]]; then
  [[ -f "${IDENTITY_FILE}" ]] || die "identity file not found: ${IDENTITY_FILE}"
fi
run_cleanup_phase "initialize remote transport if possible" init_transport_if_possible
print_summary

if [[ "${FORCE}" != "true" ]]; then
  info "no resources were deleted. Re-run with --force to execute the cleanup."
  exit 0
fi

confirm_execution

run_cleanup_phase "clean remote deployment artifacts" remote_cleanup
run_cleanup_phase "terminate EC2 instance" delete_instance
run_cleanup_phase "release Elastic IP" delete_eip
run_cleanup_phase "delete SSH key pair" delete_key_pair
run_cleanup_phase "delete security group" delete_security_group
run_cleanup_phase "delete artifact bucket" delete_bucket "${SC_S3_ARTIFACT_BUCKET}" "${SC_AWS_REGION_EFFECTIVE}" "artifact bucket"
if is_truthy "${SC_UPLOADS_ENABLED}"; then
  run_cleanup_phase "delete uploads bucket" delete_bucket "${SC_UPLOADS_BUCKET}" "${SC_UPLOADS_REGION}" "uploads bucket"
fi
run_cleanup_phase "delete discovered tagged buckets" delete_discovered_buckets
run_cleanup_phase "delete IAM role and instance profile" delete_iam_role_and_profile
run_cleanup_phase "delete SSM parameters" delete_ssm_parameters
if print_results; then
  run_cleanup_phase "clear stale resource state from enterprise env" clear_stale_env_state
else
  exit 1
fi
