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

ARCHIVE_PATH=""
DEPLOY_PATH=""
COMPOSE_FILE=""
PROJECT_NAME=""
SERVICE_NAME=""
NGINX_SOURCE=""
NGINX_TARGET=""
NGINX_COMPOSE_FILE=""
NGINX_PROJECT_NAME=""
MAINTENANCE_HOST_DIR=""
MAINTENANCE_FLAG_PATH=""

usage() {
  cat <<'EOF'
Usage:
  deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name>
  deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name> [--service <name>]
  deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name> \
    [--service <name>] [--nginx-source <path>] [--nginx-target <path>] \
    [--nginx-compose-file <path>] [--nginx-project-name <name>] \
    [--maintenance-host-dir <path>] [--maintenance-flag <path>]

This script only updates the main Goodwalk compose project at the specified
deployment path. It does not touch unrelated Docker projects or global Docker
state.
EOF
}

fail() {
  echo "[deploy-remote] ERROR: $*" >&2
  exit 1
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --archive)
      ARCHIVE_PATH="${2:-}"
      shift 2
      ;;
    --deploy-path)
      DEPLOY_PATH="${2:-}"
      shift 2
      ;;
    --compose-file)
      COMPOSE_FILE="${2:-}"
      shift 2
      ;;
    --project-name)
      PROJECT_NAME="${2:-}"
      shift 2
      ;;
    --service)
      SERVICE_NAME="${2:-}"
      shift 2
      ;;
    --nginx-source)
      NGINX_SOURCE="${2:-}"
      shift 2
      ;;
    --nginx-target)
      NGINX_TARGET="${2:-}"
      shift 2
      ;;
    --nginx-compose-file)
      NGINX_COMPOSE_FILE="${2:-}"
      shift 2
      ;;
    --nginx-project-name)
      NGINX_PROJECT_NAME="${2:-}"
      shift 2
      ;;
    --maintenance-host-dir)
      MAINTENANCE_HOST_DIR="${2:-}"
      shift 2
      ;;
    --maintenance-flag)
      MAINTENANCE_FLAG_PATH="${2:-}"
      shift 2
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      fail "Unknown argument: $1"
      ;;
  esac
done

[[ -n "$ARCHIVE_PATH" ]] || fail "--archive is required"
[[ -n "$DEPLOY_PATH" ]] || fail "--deploy-path is required"
[[ -n "$COMPOSE_FILE" ]] || fail "--compose-file is required"
[[ -n "$PROJECT_NAME" ]] || fail "--project-name is required"
if [[ -n "$SERVICE_NAME" ]]; then
  SERVICE_NAME="$(printf '%s' "$SERVICE_NAME" | xargs)"
fi
[[ "$DEPLOY_PATH" != "/" ]] || fail "Refusing to deploy to /"
[[ -f "$ARCHIVE_PATH" ]] || fail "Archive not found: $ARCHIVE_PATH"

nginx_args=("$NGINX_SOURCE" "$NGINX_TARGET" "$NGINX_COMPOSE_FILE" "$NGINX_PROJECT_NAME")
nginx_args_present=0
for value in "${nginx_args[@]}"; do
  if [[ -n "$value" ]]; then
    nginx_args_present=1
    break
  fi
done

if (( nginx_args_present )); then
  [[ -n "$NGINX_SOURCE" ]] || fail "--nginx-source is required when nginx deployment is enabled"
  [[ -n "$NGINX_TARGET" ]] || fail "--nginx-target is required when nginx deployment is enabled"
  [[ -n "$NGINX_COMPOSE_FILE" ]] || fail "--nginx-compose-file is required when nginx deployment is enabled"
  [[ -n "$NGINX_PROJECT_NAME" ]] || fail "--nginx-project-name is required when nginx deployment is enabled"
  [[ -n "$MAINTENANCE_HOST_DIR" ]] || fail "--maintenance-host-dir is required when nginx deployment is enabled"
  [[ -n "$MAINTENANCE_FLAG_PATH" ]] || fail "--maintenance-flag is required when nginx deployment is enabled"
fi

if docker compose version >/dev/null 2>&1; then
  COMPOSE_CMD=(docker compose)
elif command -v docker-compose >/dev/null 2>&1; then
  COMPOSE_CMD=(docker-compose)
else
  fail "Docker Compose is not installed on the server"
fi

STAGING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/goodwalk-deploy.XXXXXX")"
MAINTENANCE_ACTIVE=0

clear_maintenance_flag() {
  if (( MAINTENANCE_ACTIVE )) && (( nginx_args_present )); then
    echo "[deploy-remote] Clearing maintenance flag at $MAINTENANCE_FLAG_PATH"
    rm -f "$MAINTENANCE_FLAG_PATH" || true
    MAINTENANCE_ACTIVE=0
  fi
}

cleanup() {
  clear_maintenance_flag
  rm -rf "$STAGING_DIR"
}

trap cleanup EXIT

echo "[deploy-remote] Deploying main Goodwalk stack"
echo "[deploy-remote] Target deployment path: $DEPLOY_PATH"
echo "[deploy-remote] Compose file: $COMPOSE_FILE"
echo "[deploy-remote] Docker project: $PROJECT_NAME"
if [[ -n "$SERVICE_NAME" ]]; then
  echo "[deploy-remote] Target service: $SERVICE_NAME"
fi
if (( nginx_args_present )); then
  echo "[deploy-remote] Nginx config source: $NGINX_SOURCE"
  echo "[deploy-remote] Nginx config target: $NGINX_TARGET"
  echo "[deploy-remote] Nginx compose file: $NGINX_COMPOSE_FILE"
  echo "[deploy-remote] Nginx project: $NGINX_PROJECT_NAME"
  echo "[deploy-remote] Maintenance host dir: $MAINTENANCE_HOST_DIR"
  echo "[deploy-remote] Maintenance flag path: $MAINTENANCE_FLAG_PATH"
fi
echo "[deploy-remote] Staging archive in: $STAGING_DIR"

mkdir -p "$DEPLOY_PATH"
tar -xzf "$ARCHIVE_PATH" -C "$STAGING_DIR"

[[ -f "$STAGING_DIR/$COMPOSE_FILE" ]] || fail "Compose file missing from uploaded archive: $COMPOSE_FILE"

if [[ -f "$DEPLOY_PATH/.env" ]]; then
  echo "[deploy-remote] Preserving existing $DEPLOY_PATH/.env"
fi

echo "[deploy-remote] Copying application files into $DEPLOY_PATH"
if command -v rsync >/dev/null 2>&1; then
  rsync -a \
    --exclude '.env' \
    --exclude '.env.*' \
    "$STAGING_DIR"/ "$DEPLOY_PATH"/
else
  while IFS= read -r -d '' item; do
    relative_path="${item#"$STAGING_DIR"/}"

    if [[ "$relative_path" == ".env" || "$relative_path" == .env.* ]]; then
      continue
    fi

    destination="$DEPLOY_PATH/$relative_path"

    if [[ -d "$item" ]]; then
      mkdir -p "$destination"
      continue
    fi

    mkdir -p "$(dirname "$destination")"
    cp -f "$item" "$destination"
  done < <(find "$STAGING_DIR" -mindepth 1 -print0)
fi

[[ -f "$DEPLOY_PATH/$COMPOSE_FILE" ]] || fail "Compose file missing after copy: $DEPLOY_PATH/$COMPOSE_FILE"

if [[ ! -f "$DEPLOY_PATH/.env" ]]; then
  if [[ -f "$DEPLOY_PATH/deploy.env.template" ]]; then
    echo "[deploy-remote] No remote .env found. Creating $DEPLOY_PATH/.env from deploy.env.template"
    cp "$DEPLOY_PATH/deploy.env.template" "$DEPLOY_PATH/.env"
  else
    fail "Remote .env is missing and deploy.env.template was not uploaded"
  fi
fi

merge_env_file() {
  local template="$1"
  local live="$2"

  [[ -f "$template" ]] || { echo "[deploy-remote] No env template at $template, skipping merge"; return 0; }
  [[ -f "$live" ]] || { echo "[deploy-remote] No live .env at $live, skipping merge"; return 0; }

  local added diffs backup
  added="$(mktemp)"
  diffs="$(mktemp)"
  backup="${live}.bak.$(date -u +%Y%m%dT%H%M%SZ)"

  awk -v live="$live" -v added_log="$added" -v diff_log="$diffs" '
    function trim(s) { sub(/^[ \t]+/,"",s); sub(/[ \t]+$/,"",s); return s }
    BEGIN {
      while ((getline line < live) > 0) {
        if (line ~ /^[ \t]*#/ || line ~ /^[ \t]*$/) continue
        eq = index(line, "=")
        if (eq == 0) continue
        k = trim(substr(line, 1, eq-1))
        v = substr(line, eq+1)
        live_keys[k] = v
        live_seen[k] = 1
      }
      close(live)
    }
    /^[ \t]*#/ || /^[ \t]*$/ { next }
    {
      eq = index($0, "=")
      if (eq == 0) next
      k = trim(substr($0, 1, eq-1))
      v = substr($0, eq+1)
      if (!(k in live_seen)) {
        print k "=" v >> added_log
      } else if (live_keys[k] != v) {
        print k "  (template=" v "  |  live=" live_keys[k] ")" >> diff_log
      }
    }
  ' "$template"

  if [[ -s "$added" ]]; then
    cp "$live" "$backup"
    echo "[deploy-remote] Adding env keys present in template but missing from $live:"
    sed 's/^/    + /' "$added"
    {
      printf '\n# Appended by deploy-remote.sh on %s from deploy.env.template\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
      cat "$added"
    } >> "$live"
    echo "[deploy-remote] Backup of previous .env written to $backup"
  else
    echo "[deploy-remote] .env is up to date with template (no missing keys)"
  fi

  if [[ -s "$diffs" ]]; then
    echo "[deploy-remote] NOTE: these keys exist in both files but values differ. Live values are PRESERVED:"
    sed 's/^/    ! /' "$diffs"
    echo "[deploy-remote] If a live value is stale (e.g. an old OWNER_EMAIL), edit $live and re-deploy."
  fi

  rm -f "$added" "$diffs"
}

merge_env_file "$DEPLOY_PATH/deploy.env.template" "$DEPLOY_PATH/.env"

cd "$DEPLOY_PATH"

echo "[deploy-remote] Validating compose configuration"
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" config >/dev/null

if [[ -n "$SERVICE_NAME" ]]; then
  AVAILABLE_SERVICES="$("${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" config --services)"
  if ! grep -Fxq "$SERVICE_NAME" <<<"$AVAILABLE_SERVICES"; then
    fail "Service '$SERVICE_NAME' was not found in $COMPOSE_FILE. Available services: $(tr '\n' ',' <<<"$AVAILABLE_SERVICES" | sed 's/,$//')"
  fi
fi

if (( nginx_args_present )); then
  [[ -f "$DEPLOY_PATH/$NGINX_SOURCE" ]] || fail "Nginx config missing from deployment payload: $DEPLOY_PATH/$NGINX_SOURCE"
  [[ -f "$NGINX_COMPOSE_FILE" ]] || fail "Nginx compose file was not found on the server: $NGINX_COMPOSE_FILE"

  # Pre-flight: SSL certificate for onboarding.goodwalk.co.nz must exist before
  # nginx can reload — the config references this cert path directly.
  ONBOARDING_CERT="/etc/letsencrypt/live/onboarding.goodwalk.co.nz/fullchain.pem"
  ONBOARDING_KEY="/etc/letsencrypt/live/onboarding.goodwalk.co.nz/privkey.pem"
  if [[ ! -f "$ONBOARDING_CERT" || ! -f "$ONBOARDING_KEY" ]]; then
    fail "SSL certificate for onboarding.goodwalk.co.nz is not present on this server.
       Expected: $ONBOARDING_CERT
       One-time setup on the droplet:
         1. Ensure the DNS A record for onboarding.goodwalk.co.nz points to this server's IP
         2. Bring nginx up so the ACME webroot is reachable, then obtain the certificate:
              certbot certonly --webroot -w /var/www/certbot \\
                -d onboarding.goodwalk.co.nz \\
                --non-interactive --agree-tos -m info@goodwalk.co.nz
         3. Re-run this deploy script"
  fi

  MAINTENANCE_HTML_SRC="$DEPLOY_PATH/nginx/maintenance.html"
  MAINTENANCE_LOGO_SRC="$DEPLOY_PATH/nginx/logo.png"
  [[ -f "$MAINTENANCE_HTML_SRC" ]] || fail "Maintenance page missing from deployment payload: $MAINTENANCE_HTML_SRC"
  [[ -f "$MAINTENANCE_LOGO_SRC" ]] || fail "Maintenance logo missing from deployment payload: $MAINTENANCE_LOGO_SRC"

  # Pre-flight: the nginx container must have a bind mount whose source is
  # MAINTENANCE_HOST_DIR and whose destination is /var/www/maintenance. If the
  # one-time droplet setup has not been done, fail fast with a clear message
  # rather than silently serving stale content.
  NGINX_CID="$(docker ps -qf name=^nginx$ | head -n1 || true)"
  [[ -n "$NGINX_CID" ]] || fail "Shared nginx container is not running (expected name 'nginx'). Bring it up before deploying."

  if ! docker inspect -f '{{range .Mounts}}{{.Source}}|{{.Destination}}{{println}}{{end}}' "$NGINX_CID" \
        | grep -Fxq "${MAINTENANCE_HOST_DIR}|/var/www/maintenance"; then
    fail "nginx container is missing the maintenance bind mount.
       Expected: ${MAINTENANCE_HOST_DIR}:/var/www/maintenance:ro
       One-time setup on the droplet:
         mkdir -p ${MAINTENANCE_HOST_DIR}/m
         # add this volume to ${NGINX_COMPOSE_FILE}:
         #   - ${MAINTENANCE_HOST_DIR}:/var/www/maintenance:ro
         ${COMPOSE_CMD[*]} -p ${NGINX_PROJECT_NAME} -f ${NGINX_COMPOSE_FILE} up -d"
  fi

  FLAG_DIR="$(dirname "$MAINTENANCE_FLAG_PATH")"
  [[ -d "$FLAG_DIR" ]] || fail "Maintenance flag directory does not exist on host: $FLAG_DIR"

  echo "[deploy-remote] Writing maintenance assets to host bind dir: $MAINTENANCE_HOST_DIR"
  mkdir -p "$MAINTENANCE_HOST_DIR/m"
  install -m 0644 "$MAINTENANCE_HTML_SRC" "$MAINTENANCE_HOST_DIR/maintenance.html"
  install -m 0644 "$MAINTENANCE_LOGO_SRC" "$MAINTENANCE_HOST_DIR/m/logo.png"

  echo "[deploy-remote] Updating shared nginx config (pre-rebuild) so maintenance routing is active"
  mkdir -p "$(dirname "$NGINX_TARGET")"
  cp "$DEPLOY_PATH/$NGINX_SOURCE" "$NGINX_TARGET"

  echo "[deploy-remote] Validating nginx configuration"
  "${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -t

  echo "[deploy-remote] Reloading shared nginx so the new config (incl. maintenance routing) is live"
  "${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -s reload

  echo "[deploy-remote] Engaging maintenance page via host flag: $MAINTENANCE_FLAG_PATH"
  : > "$MAINTENANCE_FLAG_PATH"
  MAINTENANCE_ACTIVE=1
fi

if [[ -n "$SERVICE_NAME" ]]; then
  echo "[deploy-remote] Stopping only the Goodwalk service: $SERVICE_NAME"
  "${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" stop "$SERVICE_NAME" || true

  echo "[deploy-remote] Rebuilding and starting only the Goodwalk service: $SERVICE_NAME"
  "${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" up -d --build "$SERVICE_NAME"
else
  echo "[deploy-remote] Stopping only the Goodwalk project containers"
  "${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" stop || true

  echo "[deploy-remote] Rebuilding and starting only the Goodwalk project containers"
  "${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" up -d --build --remove-orphans
fi

echo "[deploy-remote] Current Goodwalk container status"
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" ps

if [[ -z "$SERVICE_NAME" || "$SERVICE_NAME" == "app" || "$SERVICE_NAME" == "db" ]]; then
  echo "[deploy-remote] Syncing homepage content into PostgreSQL"
  "${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" exec -T app node scripts/sync-homepage-content.mjs
fi

clear_maintenance_flag

echo "[deploy-remote] Remote deployment finished"
