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

SCRIPT_VERSION="v0.0.0-system-caddy"
BASE_URL="${FLYGB_INSTALL_BASE_URL:-https://install.flygb.shop}"
SOURCE_ARCHIVE="${FLYGB_SOURCE_ARCHIVE:-${BASE_URL}/releases/v0.0.0/flygb-source.tar.gz}"
CHECKSUMS_URL="${FLYGB_CHECKSUMS_URL:-${BASE_URL}/checksums.txt}"
INSTALL_DIR="${FLYGB_INSTALL_DIR:-/opt/flygb}"

FLYGB_WEB_DOMAIN="${FLYGB_WEB_DOMAIN:-flygb.shop}"
FLYGB_ADMIN_DOMAIN="${FLYGB_ADMIN_DOMAIN:-admin.flygb.shop}"
FLYGB_API_DOMAIN="${FLYGB_API_DOMAIN:-api.flygb.shop}"
CADDY_ACME_EMAIL="${CADDY_ACME_EMAIL:-admin@flygb.shop}"
ADMIN_EMAIL="${ADMIN_EMAIL:-admin@flygb.shop}"
ADMIN_NAME="${ADMIN_NAME:-FlyGB Super Admin}"
ADMIN_BASIC_AUTH_USER="${ADMIN_BASIC_AUTH_USER:-flygbadmin}"

log() {
  printf '[flygb-install:%s] %s\n' "${SCRIPT_VERSION}" "$*"
}

fail() {
  printf '[flygb-install:%s] ERROR: %s\n' "${SCRIPT_VERSION}" "$*" >&2
  exit 1
}

require_root() {
  if [ "$(id -u)" -ne 0 ]; then
    fail "Run with root privileges, for example: curl -fsSL ${BASE_URL}/install.sh | sudo bash"
  fi
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || fail "Missing required command: $1"
}

random_hex() {
  openssl rand -hex "${1:-32}"
}

install_packages() {
  export DEBIAN_FRONTEND=noninteractive
  apt-get update
  apt-get install -y ca-certificates curl git openssl tar gzip caddy
}

install_docker_if_needed() {
  if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
    return
  fi

  log "Installing Docker using the official convenience script."
  curl -fsSL https://get.docker.com | sh
  command -v docker >/dev/null 2>&1 || fail "Docker installation failed."
  docker compose version >/dev/null 2>&1 || fail "Docker Compose plugin is unavailable."
}

guard_download_server() {
  local public_ip
  public_ip="$(curl -4 -fsS --max-time 8 https://ifconfig.me 2>/dev/null || true)"
  if [ "${public_ip}" = "137.59.19.66" ] && [ "${FLYGB_ALLOW_DOWNLOAD_SERVER_INSTALL:-false}" != "true" ]; then
    fail "This looks like the download server 137.59.19.66. Run this installer on production server 156.234.79.82."
  fi
}

download_source() {
  local tmp_dir archive checksums expected actual
  tmp_dir="$(mktemp -d)"
  archive="${tmp_dir}/flygb-source.tar.gz"
  checksums="${tmp_dir}/checksums.txt"

  log "Downloading FlyGB source archive."
  curl -fsSL "${SOURCE_ARCHIVE}" -o "${archive}"
  curl -fsSL "${CHECKSUMS_URL}" -o "${checksums}"

  expected="$(awk '$2 == "releases/v0.0.0/flygb-source.tar.gz" || $2 == "flygb-source.tar.gz" { print $1; exit }' "${checksums}")"
  [ -n "${expected}" ] || fail "Missing checksum for flygb-source.tar.gz."
  actual="$(sha256sum "${archive}" | awk '{ print $1 }')"
  [ "${actual}" = "${expected}" ] || fail "SHA256 mismatch for FlyGB source archive."

  mkdir -p "${INSTALL_DIR}"
  tar -xzf "${archive}" -C "${INSTALL_DIR}" --strip-components=1
  chmod +x "${INSTALL_DIR}"/scripts/deploy/*.sh 2>/dev/null || true
  rm -rf "${tmp_dir}"
}

write_env_file() {
  cd "${INSTALL_DIR}"
  if [ -f .env.production ] && [ "${FLYGB_OVERWRITE_ENV:-false}" != "true" ]; then
    log "Keeping existing ${INSTALL_DIR}/.env.production."
    return
  fi

  local postgres_password admin_password esim_key order_secret mock_webhook_secret
  postgres_password="${POSTGRES_PASSWORD:-$(random_hex 24)}"
  admin_password="${ADMIN_PASSWORD:-$(random_hex 18)}"
  esim_key="${ESIM_PROFILE_ENCRYPTION_KEY:-$(random_hex 32)}"
  order_secret="${ORDER_ACCESS_TOKEN_SECRET:-$(random_hex 32)}"
  mock_webhook_secret="${MOCK_PAYMENT_WEBHOOK_SECRET:-$(random_hex 32)}"

  log "Writing production environment file with locally generated secrets."
  cat >.env.production <<EOF
NODE_ENV=production

FLYGB_WEB_DOMAIN=${FLYGB_WEB_DOMAIN}
FLYGB_ADMIN_DOMAIN=${FLYGB_ADMIN_DOMAIN}
FLYGB_API_DOMAIN=${FLYGB_API_DOMAIN}
CADDY_ACME_EMAIL=${CADDY_ACME_EMAIL}

POSTGRES_DB=flygb
POSTGRES_USER=flygb
POSTGRES_PASSWORD=${postgres_password}
DATABASE_URL=postgresql://flygb:${postgres_password}@postgres:5432/flygb?schema=public
DIRECT_URL=postgresql://flygb:${postgres_password}@postgres:5432/flygb?schema=public

REDIS_URL=redis://redis:6379
WORKER_ENABLE_QUEUE_CONNECTION=true
WORKER_HEALTH_QUEUE_NAME=flygb.health
WORKER_CONCURRENCY=2

WEB_PORT=3000
ADMIN_PORT=3001
API_PORT=4000
NEXT_PUBLIC_API_BASE_URL=https://${FLYGB_API_DOMAIN}/api/v1
NEXT_PUBLIC_SITE_URL=https://${FLYGB_WEB_DOMAIN}
SITE_URL=https://${FLYGB_WEB_DOMAIN}
ADMIN_API_BASE_URL=https://${FLYGB_API_DOMAIN}
CORS_ORIGINS=https://${FLYGB_WEB_DOMAIN},https://${FLYGB_ADMIN_DOMAIN}

ADMIN_API_BEARER_TOKEN=
WEB_API_BEARER_TOKEN=
WEB_ORDER_ACCESS_TOKEN=

PAYMENT_PROVIDER=mock
SUPPLIER_PROVIDER=mock
EMAIL_PROVIDER=mock

CURRENT_SUPPLIER_API_BASE_URL=
CURRENT_SUPPLIER_API_KEY=
CURRENT_SUPPLIER_API_SECRET=
CURRENT_SUPPLIER_ACCOUNT_ID=
CURRENT_SUPPLIER_SIGNATURE_ENABLED=true
CURRENT_SUPPLIER_API_KEY_HEADER=x-api-key
CURRENT_SUPPLIER_ACCOUNT_ID_HEADER=x-account-id
CURRENT_SUPPLIER_TIMESTAMP_HEADER=x-timestamp
CURRENT_SUPPLIER_SIGNATURE_HEADER=x-signature
CURRENT_SUPPLIER_IDEMPOTENCY_HEADER=idempotency-key
CURRENT_SUPPLIER_TIMEOUT_MS=10000
CURRENT_SUPPLIER_MAX_RETRIES=2
CURRENT_SUPPLIER_RETRY_DELAY_MS=500

MOCK_PAYMENT_CHECKOUT_BASE_URL=https://${FLYGB_API_DOMAIN}/mock-payment/checkout
MOCK_PAYMENT_WEBHOOK_SECRET=${mock_webhook_secret}

STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_API_BASE_URL=https://api.stripe.com
STRIPE_TIMEOUT_MS=10000
STRIPE_WEBHOOK_TOLERANCE_SECONDS=300

PAYMENT_FULFILLMENT_QUEUE_NAME=flygb.payment.fulfill
PAYMENT_FULFILLMENT_DEAD_LETTER_QUEUE_NAME=flygb.dead-letter.review
PAYMENT_FULFILLMENT_MAX_ATTEMPTS=3
PAYMENT_FULFILLMENT_BACKOFF_DELAY_MS=60000

ESIM_PROFILE_ENCRYPTION_KEY=${esim_key}
ORDER_ACCESS_TOKEN_SECRET=${order_secret}
ORDER_ACCESS_CODE_TTL_SECONDS=900
ORDER_ACCESS_TOKEN_TTL_SECONDS=3600

MOCK_EMAIL_FORCE_FAIL=false
MOCK_EMAIL_FAIL_RECIPIENTS=
RESEND_API_KEY=
RESEND_FROM_EMAIL=
RESEND_API_BASE_URL=https://api.resend.com
RESEND_TIMEOUT_MS=10000
RESEND_MAX_RETRIES=2
RESEND_RETRY_DELAY_MS=500

SMOKE_API_BASE_URL=https://${FLYGB_API_DOMAIN}
SMOKE_CUSTOMER_EMAIL=smoke@flygb.local
SMOKE_ORDER_TIMEOUT_MS=60000

ADMIN_EMAIL=${ADMIN_EMAIL}
ADMIN_PASSWORD=${admin_password}
ADMIN_NAME="${ADMIN_NAME}"
ADMIN_SESSION_TTL_SECONDS=28800
CHECKOUT_QUOTE_TTL_SECONDS=900
EOF

  chmod 600 .env.production
  printf '%s\n' "${admin_password}" >.admin-password.generated
  chmod 600 .admin-password.generated
}

write_admin_basic_auth_file() {
  local password
  password="${ADMIN_BASIC_AUTH_PASSWORD:-$(random_hex 16)}"

  mkdir -p "${INSTALL_DIR}"
  cat >"${INSTALL_DIR}/.admin-basic-auth.generated" <<EOF
ADMIN_BASIC_AUTH_USER=${ADMIN_BASIC_AUTH_USER}
ADMIN_BASIC_AUTH_PASSWORD=${password}
EOF
  chmod 600 "${INSTALL_DIR}/.admin-basic-auth.generated"
}

write_system_caddy_compose_override() {
  cd "${INSTALL_DIR}"
  cat >docker-compose.system-caddy.yml <<'EOF'
services:
  caddy:
    profiles:
      - disabled

  web:
    ports:
      - "127.0.0.1:13000:3000"

  admin:
    ports:
      - "127.0.0.1:13001:3001"

  api:
    ports:
      - "127.0.0.1:14000:4000"
EOF
}

compose() {
  docker compose \
    --env-file "${INSTALL_DIR}/.env.production" \
    -f "${INSTALL_DIR}/docker-compose.prod.yml" \
    -f "${INSTALL_DIR}/docker-compose.system-caddy.yml" \
    "$@"
}

start_flygb() {
  cd "${INSTALL_DIR}"
  log "Building production images."
  compose build

  log "Starting PostgreSQL and Redis."
  compose up -d postgres redis

  log "Running database migrations."
  compose run --rm api npx prisma migrate deploy --schema packages/db/prisma/schema.prisma

  log "Seeding baseline data and admin user."
  compose run --rm api npm run db:seed

  if [ "${FLYGB_RUN_PRODUCT_SYNC:-true}" = "true" ]; then
    log "Running initial product sync."
    compose run --rm worker npm run worker:sync-products
  fi

  log "Starting FlyGB services."
  compose up -d --remove-orphans
}

configure_system_caddy() {
  local caddyfile tmp admin_basic_auth_password admin_basic_auth_hash
  caddyfile="/etc/caddy/Caddyfile"
  tmp="$(mktemp)"
  # shellcheck disable=SC1090
  source "${INSTALL_DIR}/.admin-basic-auth.generated"
  admin_basic_auth_password="${ADMIN_BASIC_AUTH_PASSWORD:?ADMIN_BASIC_AUTH_PASSWORD is required}"
  admin_basic_auth_hash="$(caddy hash-password --plaintext "${admin_basic_auth_password}")"

  mkdir -p /etc/caddy
  touch "${caddyfile}"
  cp "${caddyfile}" "${caddyfile}.bak.$(date -u +%Y%m%dT%H%M%SZ)"

  awk '
    /# flygb managed block start/ {skip=1; next}
    /# flygb managed block end/ {skip=0; next}
    skip != 1 {print}
  ' "${caddyfile}" >"${tmp}"
  mv "${tmp}" "${caddyfile}"
  chown root:root "${caddyfile}"
  chmod 0644 "${caddyfile}"

  cat >>"${caddyfile}" <<EOF

# flygb managed block start
${FLYGB_WEB_DOMAIN} {
        encode zstd gzip
        reverse_proxy 127.0.0.1:13000
}

www.${FLYGB_WEB_DOMAIN} {
        redir https://${FLYGB_WEB_DOMAIN}{uri} permanent
}

${FLYGB_ADMIN_DOMAIN} {
        encode zstd gzip
        basicauth {
                ${ADMIN_BASIC_AUTH_USER} ${admin_basic_auth_hash}
        }
        reverse_proxy 127.0.0.1:13001
}

${FLYGB_API_DOMAIN} {
        encode zstd gzip
        reverse_proxy 127.0.0.1:14000
}
# flygb managed block end
EOF
  chown root:root "${caddyfile}"
  chmod 0644 "${caddyfile}"

  systemctl enable --now caddy
  caddy validate --config "${caddyfile}"
  systemctl reload caddy
}

print_summary() {
  compose ps || true
  log "Install complete."
  log "Web: https://${FLYGB_WEB_DOMAIN}"
  log "Admin: https://${FLYGB_ADMIN_DOMAIN}"
  log "API health: https://${FLYGB_API_DOMAIN}/api/v1/health"
  log "Admin password: ${INSTALL_DIR}/.admin-password.generated"
}

main() {
  require_root
  install_packages
  require_command curl
  require_command openssl
  require_command sha256sum
  guard_download_server
  install_docker_if_needed
  download_source
  write_env_file
  write_admin_basic_auth_file
  write_system_caddy_compose_override
  start_flygb
  configure_system_caddy
  print_summary
}

main "$@"
