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

REPO_URL=""
BRANCH="main"
REF=""
DEPLOY_PATH=""
COMPOSE_FILE=""
PROJECT_NAME=""
SERVICE_NAME=""
NGINX_SOURCE=""
NGINX_TARGET=""
NGINX_COMPOSE_FILE=""
NGINX_PROJECT_NAME=""
MAINTENANCE_HOST_DIR=""
MAINTENANCE_FLAG_PATH=""
VERIFY_URL="https://www.goodwalk.co.nz/api/health"
SKIP_SITE_CHECK=0

usage() {
  cat <<'EOF'
Usage:
  deploy-from-git.sh --repo-url <url> --branch <name> --deploy-path <path> --compose-file <name> --project-name <name>
  deploy-from-git.sh --repo-url <url> [--branch <name>] [--ref <commit-or-tag>] --deploy-path <path> --compose-file <name> --project-name <name> [--service <name>]
  deploy-from-git.sh --repo-url <url> [--branch <name>] [--ref <commit-or-tag>] --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>] \
    [--verify-url <url>] [--skip-site-check]

This script clones or fetches the application repo on the server, exports the
homepage content payload, updates only the main Goodwalk compose project, and
optionally updates the shared nginx stack plus maintenance mode handling.

Authentication for private HTTPS repos is expected to come from ~/.netrc,
git-credential, or another Git-supported credential mechanism already present
on the server.
EOF
}

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

assert_command() {
  command -v "$1" >/dev/null 2>&1 || fail "Required command '$1' is not installed on the server"
}

run_homepage_export() {
  local export_script="$1"
  local output_path="$2"

  if command -v node >/dev/null 2>&1; then
    node --experimental-strip-types "$export_script" "$output_path"
    return
  fi

  echo "[deploy-git] Host node not found; exporting homepage content via temporary node:22-alpine container"
  docker run --rm \
    -v "$CHECKOUT_DIR:/app" \
    -w /app \
    node:22-alpine \
    node --experimental-strip-types "${export_script#/app/}" "${output_path#/app/}"
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --repo-url)
      REPO_URL="${2:-}"
      shift 2
      ;;
    --branch)
      BRANCH="${2:-}"
      shift 2
      ;;
    --ref)
      REF="${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
      ;;
    --verify-url)
      VERIFY_URL="${2:-}"
      shift 2
      ;;
    --skip-site-check)
      SKIP_SITE_CHECK=1
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      fail "Unknown argument: $1"
      ;;
  esac
done

[[ -n "$REPO_URL" ]] || fail "--repo-url is required"
[[ -n "$BRANCH" ]] || fail "--branch 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 /"

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

assert_command git
assert_command docker
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

WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/goodwalk-git-deploy.XXXXXX")"
CHECKOUT_DIR="$WORK_DIR/repo"
PAYLOAD_DIR="$WORK_DIR/payload"
MAINTENANCE_ACTIVE=0

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

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

copy_checkout_to_payload() {
  mkdir -p "$PAYLOAD_DIR"

  if command -v rsync >/dev/null 2>&1; then
    rsync -a \
      --exclude '.git' \
      --exclude '.env' \
      --exclude '.env.*' \
      "$CHECKOUT_DIR"/ "$PAYLOAD_DIR"/
    return
  fi

  while IFS= read -r -d '' item; do
    relative_path="${item#"$CHECKOUT_DIR"/}"

    case "$relative_path" in
      .git|.git/*|.env|.env.*)
        continue
        ;;
    esac

    destination="$PAYLOAD_DIR/$relative_path"

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

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

copy_payload_to_deploy() {
  mkdir -p "$DEPLOY_PATH"

  if command -v rsync >/dev/null 2>&1; then
    rsync -a \
      --exclude '.env' \
      --exclude '.env.*' \
      "$PAYLOAD_DIR"/ "$DEPLOY_PATH"/
    return
  fi

  while IFS= read -r -d '' item; do
    relative_path="${item#"$PAYLOAD_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 "$PAYLOAD_DIR" -mindepth 1 -print0)
}

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

  [[ -f "$template" ]] || { echo "[deploy-git] No env template at $template, skipping merge"; return 0; }
  [[ -f "$live" ]] || { echo "[deploy-git] 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-git] Adding env keys present in template but missing from $live:"
    sed 's/^/    + /' "$added"
    {
      printf '\n# Appended by deploy-from-git.sh on %s from deploy.env.template\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
      cat "$added"
    } >> "$live"
    echo "[deploy-git] Backup of previous .env written to $backup"
  else
    echo "[deploy-git] .env is up to date with template (no missing keys)"
  fi

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

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

check_site() {
  if (( SKIP_SITE_CHECK )) || [[ -z "$VERIFY_URL" ]]; then
    return 0
  fi

  echo "[deploy-git] Checking production site: $VERIFY_URL"

  if command -v curl >/dev/null 2>&1; then
    local http_code
    if http_code="$(curl -fsS -o /dev/null -w '%{http_code}' --max-time 30 -L "$VERIFY_URL" 2>/dev/null)"; then
      echo "[deploy-git] Site responded with HTTP $http_code"
    else
      echo "[deploy-git] WARNING: production site check failed for $VERIFY_URL" >&2
    fi
    return 0
  fi

  if command -v wget >/dev/null 2>&1; then
    if wget --spider --server-response --timeout=30 "$VERIFY_URL" >/tmp/goodwalk-site-check.$$ 2>&1; then
      awk '/^  HTTP\// { code=$2 } END { if (code != "") printf "[deploy-git] Site responded with HTTP %s\n", code }' /tmp/goodwalk-site-check.$$
    else
      echo "[deploy-git] WARNING: production site check failed for $VERIFY_URL" >&2
    fi
    rm -f /tmp/goodwalk-site-check.$$
    return 0
  fi

  echo "[deploy-git] WARNING: curl/wget not available; skipping site check" >&2
}

trap cleanup EXIT

echo "[deploy-git] Deploying main Goodwalk stack from Git"
echo "[deploy-git] Repo URL: $REPO_URL"
echo "[deploy-git] Branch: $BRANCH"
if [[ -n "$REF" ]]; then
  echo "[deploy-git] Requested ref: $REF"
fi
echo "[deploy-git] Target deployment path: $DEPLOY_PATH"
echo "[deploy-git] Compose file: $COMPOSE_FILE"
echo "[deploy-git] Docker project: $PROJECT_NAME"
if [[ -n "$SERVICE_NAME" ]]; then
  echo "[deploy-git] Target service: $SERVICE_NAME"
fi
if (( nginx_args_present )); then
  echo "[deploy-git] Nginx config source: $NGINX_SOURCE"
  echo "[deploy-git] Nginx config target: $NGINX_TARGET"
  echo "[deploy-git] Nginx compose file: $NGINX_COMPOSE_FILE"
  echo "[deploy-git] Nginx project: $NGINX_PROJECT_NAME"
  echo "[deploy-git] Maintenance host dir: $MAINTENANCE_HOST_DIR"
  echo "[deploy-git] Maintenance flag path: $MAINTENANCE_FLAG_PATH"
fi

echo "[deploy-git] Cloning repository into: $CHECKOUT_DIR"
git clone "$REPO_URL" "$CHECKOUT_DIR"
git -C "$CHECKOUT_DIR" fetch --tags --prune origin

if [[ -n "$REF" ]]; then
  git -C "$CHECKOUT_DIR" checkout --detach "$REF"
else
  git -C "$CHECKOUT_DIR" checkout -B "$BRANCH" "origin/$BRANCH"
fi

DEPLOYED_REVISION="$(git -C "$CHECKOUT_DIR" rev-parse HEAD)"
echo "[deploy-git] Using repo revision: $DEPLOYED_REVISION"

EXPORT_SCRIPT="$CHECKOUT_DIR/scripts/export-homepage-content.mjs"
[[ -f "$EXPORT_SCRIPT" ]] || fail "Homepage export script not found: $EXPORT_SCRIPT"
echo "[deploy-git] Exporting current homepage content for PostgreSQL sync"
run_homepage_export "/app/scripts/export-homepage-content.mjs" "/app/deploy-data/homepage-content.json"

echo "[deploy-git] Preparing deployment payload"
copy_checkout_to_payload

[[ -f "$PAYLOAD_DIR/$COMPOSE_FILE" ]] || fail "Compose file missing from repo checkout: $COMPOSE_FILE"

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

echo "[deploy-git] Copying application files into $DEPLOY_PATH"
copy_payload_to_deploy

[[ -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-git] 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 present"
  fi
fi

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

cd "$DEPLOY_PATH"

echo "[deploy-git] 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"

  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"

  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-git] 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-git] 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-git] Validating nginx configuration"
  "${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -t

  echo "[deploy-git] 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-git] Engaging maintenance page via host flag: $MAINTENANCE_FLAG_PATH"
  : > "$MAINTENANCE_FLAG_PATH"
  MAINTENANCE_ACTIVE=1
fi

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

  echo "[deploy-git] 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-git] Stopping only the Goodwalk project containers"
  "${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" stop || true

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

echo "[deploy-git] 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-git] 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
check_site

echo "[deploy-git] Remote deployment finished"
echo "[deploy-git] Deployed revision: $DEPLOYED_REVISION"
