#!/bin/bash
set -euo pipefail

script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
root_dir="$(cd -- "$script_dir/.." && pwd)"

local_folder="${LOCAL_FOLDER:-$root_dir}"
remote_folder="${REMOTE_FOLDER:-/opt/www/virtual/SkinbaseNova}"
remote_server="${REMOTE_SERVER:-klevze@server3.klevze.si}"
php_bin="${PHP_BIN:-php}"
composer_bin="${COMPOSER_BIN:-composer}"
ssh_bin="${SSH_BIN:-ssh}"
rsync_bin="${RSYNC_BIN:-rsync}"
local_build_command="${LOCAL_BUILD_COMMAND:-}"

run_local_build=1
run_remote_migrations=1
run_db_sync=0
run_meilisearch_setup=0
auto_detect_meilisearch=1
db_sync_source=""
legacy_db_sync_mode=0
force_db_sync=0
skip_maintenance=0
db_sync_confirm_target="${DB_SYNC_CONFIRM_TARGET:-}"
db_sync_confirm_phrase="${DB_SYNC_CONFIRM_PHRASE:-}"
meilisearch_models_csv=""
readonly all_meilisearch_models_csv='App\Models\Artwork,App\Models\User,App\Models\Group,App\Models\Post,App\Models\Message'

usage() {
  cat <<'EOF'
Usage: bash sync.sh [options]

Options:
  --skip-build          Skip local npm build before rsync.
  --skip-migrate        Skip php artisan migrate on the server.
  --with-db-from=local  Replace the production database with a dump from the local database.
  --confirm-db-sync-target HOST
                        Must match the remote server name when running non-interactively.
  --confirm-db-sync-phrase TEXT
                        Must equal 'replace production db from local' when running non-interactively.
  --with-db             Legacy alias for --with-db-from=local.
  --force-db-sync       Legacy extra confirmation flag for --with-db.
  --with-meilisearch    Force Meilisearch settings sync and reimport all searchable models.
  --skip-meilisearch    Skip Meilisearch refresh, including auto-detected refreshes.
  --no-maintenance      Skip php artisan down/up during deploy.
  --help                Show this help.

Environment overrides:
  LOCAL_FOLDER, REMOTE_FOLDER, REMOTE_SERVER, PHP_BIN, COMPOSER_BIN, SSH_BIN, RSYNC_BIN,
  LOCAL_BUILD_COMMAND, DB_SYNC_CONFIRM_TARGET, DB_SYNC_CONFIRM_PHRASE
EOF
}

confirm_database_replacement() {
  local expected_phrase="replace production db from local"
  local typed_target=""
  local typed_phrase=""

  if [[ "$run_db_sync" -ne 1 || "$db_sync_source" != "local" ]]; then
    return
  fi

  echo "WARNING: this will overwrite the production database on $remote_server using your local database dump."

  if [[ -n "$db_sync_confirm_target" || -n "$db_sync_confirm_phrase" ]]; then
    if [[ "$db_sync_confirm_target" != "$remote_server" ]]; then
      echo "Refusing DB sync: --confirm-db-sync-target must exactly match $remote_server." >&2
      exit 1
    fi

    if [[ "$db_sync_confirm_phrase" != "$expected_phrase" ]]; then
      echo "Refusing DB sync: --confirm-db-sync-phrase must exactly equal '$expected_phrase'." >&2
      exit 1
    fi

    return
  fi

  if [[ ! -t 0 ]]; then
    echo "Refusing DB sync in non-interactive mode without --confirm-db-sync-target \"$remote_server\" and --confirm-db-sync-phrase \"$expected_phrase\"." >&2
    exit 1
  fi

  read -r -p "Type the remote server to confirm DB replacement [$remote_server]: " typed_target
  if [[ "$typed_target" != "$remote_server" ]]; then
    echo "Refusing DB sync: remote server confirmation did not match." >&2
    exit 1
  fi

  read -r -p "Type '$expected_phrase' to continue: " typed_phrase
  if [[ "$typed_phrase" != "$expected_phrase" ]]; then
    echo "Refusing DB sync: confirmation phrase did not match." >&2
    exit 1
  fi
}

is_wsl() {
  [[ -n "${WSL_DISTRO_NAME:-}" || -n "${WSL_INTEROP:-}" ]]
}

run_frontend_build() {
  if [[ -n "$local_build_command" ]]; then
    (
      cd "$local_folder"
      eval "$local_build_command"
    )
    return
  fi

  if is_wsl && command -v wslpath >/dev/null 2>&1 && command -v powershell.exe >/dev/null 2>&1; then
    local windows_local_folder
    windows_local_folder="$(wslpath -w "$local_folder")"

    echo "Detected WSL checkout; running frontend build with Windows npm.cmd to match local node_modules..."
    powershell.exe -NoProfile -ExecutionPolicy Bypass -Command \
      "Set-Location -LiteralPath '$windows_local_folder'; npm.cmd run build"
    return
  fi

  (
    cd "$local_folder"
    npm run build
  )
}

build_rsync_args() {
  rsync_args=(
    -rlvz
    --no-perms
    --no-times
    --omit-dir-times
    --delete
    --delete-delay
    --exclude ".phpintel/"
    --exclude "bootstrap/cache/"
    --exclude ".env"
    --exclude "public/hot"
    --exclude "node_modules"
    --exclude "public/files/"
    --exclude "resources/lang/"
    --exclude "storage/"
    --exclude ".git/"
    --exclude ".cursor/"
    --exclude ".venv/"
    --exclude "/var/php-tmp"
    --exclude "/oldSite"
    --exclude "/vendor"
    -e "$ssh_bin"
  )
}

collect_sync_changed_files() {
  local itemized

  if ! itemized="$($rsync_bin "${rsync_args[@]}" --dry-run --itemize-changes "$local_folder/" "$remote_server:$remote_folder/" 2>/dev/null)"; then
    return 1
  fi

  printf '%s\n' "$itemized" | awk '
    /^deleting / {
      sub(/^deleting /, "", $0)
      if ($0 !~ /\/$/) print
      next
    }
    /^[<>ch.*][^ ]* / {
      path = $0
      sub(/^[^ ]+ /, "", path)
      if (path !~ /\/$/) print path
    }
  ' | sed '/^$/d' | sort -u
}

detect_meilisearch_models_from_sync() {
  local changed_files
  local file
  local force_full=0
  local -a models=()

  if ! changed_files="$(collect_sync_changed_files)"; then
    return 1
  fi

  [[ -n "$changed_files" ]] || return 1

  while IFS= read -r file; do
    case "$file" in
      config/scout.php|app/Console/Commands/ConfigureMeilisearchIndex.php)
        force_full=1
        ;;
      app/Models/Artwork.php)
        models+=("App\Models\Artwork")
        ;;
      app/Models/User.php)
        models+=("App\Models\User")
        ;;
      app/Models/Group.php)
        models+=("App\Models\Group")
        ;;
      app/Models/Post.php)
        models+=("App\Models\Post")
        ;;
      app/Models/Message.php)
        models+=("App\Models\Message")
        ;;
    esac
  done <<< "$changed_files"

  if [[ "$force_full" -eq 1 ]]; then
    printf '%s\n' "$all_meilisearch_models_csv"
    return 0
  fi

  [[ ${#models[@]} -gt 0 ]] || return 1

  printf '%s\n' "$(printf '%s\n' "${models[@]}" | awk '!seen[$0]++' | paste -sd, -)"
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --skip-build)
      run_local_build=0
      ;;
    --skip-migrate)
      run_remote_migrations=0
      ;;
    --with-db-from=local)
      run_db_sync=1
      db_sync_source="local"
      ;;
    --with-db-from=*)
      echo "Unsupported DB sync source in option: $1" >&2
      echo "Only --with-db-from=local is supported." >&2
      exit 1
      ;;
    --with-db)
      run_db_sync=1
      db_sync_source="local"
      legacy_db_sync_mode=1
      ;;
    --force-db-sync)
      force_db_sync=1
      ;;
    --confirm-db-sync-target)
      shift
      db_sync_confirm_target="${1:?Missing value for --confirm-db-sync-target}"
      ;;
    --confirm-db-sync-target=*)
      db_sync_confirm_target="${1#*=}"
      ;;
    --confirm-db-sync-phrase)
      shift
      db_sync_confirm_phrase="${1:?Missing value for --confirm-db-sync-phrase}"
      ;;
    --confirm-db-sync-phrase=*)
      db_sync_confirm_phrase="${1#*=}"
      ;;
    --with-meilisearch)
      run_meilisearch_setup=1
      auto_detect_meilisearch=0
      meilisearch_models_csv="$all_meilisearch_models_csv"
      ;;
    --skip-meilisearch)
      run_meilisearch_setup=0
      auto_detect_meilisearch=0
      meilisearch_models_csv=""
      ;;
    --no-maintenance)
      skip_maintenance=1
      ;;
    --help|-h)
      usage
      exit 0
      ;;
    *)
      echo "Unknown option: $1" >&2
      usage >&2
      exit 1
      ;;
  esac
  shift
done

if [[ "$run_db_sync" -eq 1 && "$db_sync_source" != "local" ]]; then
  echo "Refusing DB sync without an explicit source. Use --with-db-from=local." >&2
  exit 1
fi

if [[ "$run_db_sync" -eq 1 ]] && { [[ -z "$db_sync_confirm_target" && -n "$db_sync_confirm_phrase" ]] || [[ -n "$db_sync_confirm_target" && -z "$db_sync_confirm_phrase" ]]; }; then
  echo "Refusing DB sync: both --confirm-db-sync-target and --confirm-db-sync-phrase are required together when provided." >&2
  exit 1
fi

if [[ "$legacy_db_sync_mode" -eq 1 && "$force_db_sync" -ne 1 ]]; then
  echo "Refusing legacy --with-db without --force-db-sync. Prefer --with-db-from=local instead." >&2
  exit 1
fi

if [[ "$run_db_sync" -eq 1 ]]; then
  confirm_database_replacement
fi

if [[ "$run_local_build" -eq 1 ]]; then
  echo "Building frontend assets locally..."
  run_frontend_build
fi

build_rsync_args

if [[ "$run_meilisearch_setup" -eq 0 && "$auto_detect_meilisearch" -eq 1 ]]; then
  if meilisearch_models_csv="$(detect_meilisearch_models_from_sync)"; then
    run_meilisearch_setup=1
    echo "Detected Meilisearch-relevant changes in this deployment; will refresh indexes for: $meilisearch_models_csv"
  fi
fi

echo "Syncing application files to $remote_server..."
"$rsync_bin" "${rsync_args[@]}" "$local_folder/" "$remote_server:$remote_folder/"

if [[ "$run_db_sync" -eq 1 ]]; then
  echo "Replacing the production database from the local dump..."
  "$script_dir/push-db-to-prod.sh" \
    --force \
    --remote-server "$remote_server" \
    --remote-folder "$remote_folder" \
    $( [[ "$run_remote_migrations" -eq 0 ]] && printf '%s' '--skip-migrate' )
fi

echo "Running remote Composer and Artisan steps..."
"$ssh_bin" "$remote_server" \
  REMOTE_FOLDER="$(printf '%q' "$remote_folder")" \
  PHP_BIN="$(printf '%q' "$php_bin")" \
  COMPOSER_BIN="$(printf '%q' "$composer_bin")" \
  RUN_REMOTE_MIGRATIONS="$run_remote_migrations" \
  SKIP_MAINTENANCE="$skip_maintenance" \
  RUN_MEILISEARCH_SETUP="$run_meilisearch_setup" \
  MEILISEARCH_MODELS_CSV="$(printf '%q' "$meilisearch_models_csv")" \
  'bash -s' <<'EOF'
set -euo pipefail

cd "$REMOTE_FOLDER"

ensure_php_runtime_dir() {
  local target_dir="$1"
  local -a privileged_cmd=()

  if command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1; then
    privileged_cmd=(sudo -n)
  elif [[ "$(id -u)" -eq 0 ]]; then
    privileged_cmd=()
  fi

  if [[ ! -d "$target_dir" ]]; then
    if [[ ${#privileged_cmd[@]} -gt 0 ]]; then
      "${privileged_cmd[@]}" mkdir -p "$target_dir"
    else
      mkdir -p "$target_dir"
    fi
  fi

  if [[ ${#privileged_cmd[@]} -gt 0 || "$(id -u)" -eq 0 ]]; then
    "${privileged_cmd[@]}" chown -R skinbase:skinbase "$target_dir"
    "${privileged_cmd[@]}" chmod 770 "$target_dir"
    return
  fi

  chmod 770 "$target_dir" >/dev/null 2>&1 || true
}

ensure_php_runtime_dir "$REMOTE_FOLDER/var/php-tmp"
ensure_php_runtime_dir "$REMOTE_FOLDER/var/php-sessions"

bring_app_up() {
  if [[ "$SKIP_MAINTENANCE" -eq 0 ]]; then
    "$PHP_BIN" artisan up >/dev/null 2>&1 || true
  fi
}

trap bring_app_up EXIT

if [[ "$SKIP_MAINTENANCE" -eq 0 ]]; then
  "$PHP_BIN" artisan down --retry=60 || true
fi

"$COMPOSER_BIN" install --no-dev --prefer-dist --optimize-autoloader --no-interaction

if [[ "$RUN_REMOTE_MIGRATIONS" -eq 1 ]]; then
  "$PHP_BIN" artisan migrate --force
fi

"$PHP_BIN" artisan optimize:clear
"$PHP_BIN" artisan optimize

if ! "$PHP_BIN" artisan homepage:warm-guest-cache; then
  echo "Warning: homepage guest cache warm failed during deploy." >&2
fi

if ! "$PHP_BIN" artisan posts:warm-trending; then
  echo "Warning: post trending cache warm failed during deploy." >&2
fi

"$PHP_BIN" artisan queue:restart || true
"$PHP_BIN" artisan horizon:terminate || true

if [[ "$RUN_MEILISEARCH_SETUP" -eq 1 ]]; then
  if [[ -z "${MEILISEARCH_MODELS_CSV:-}" ]]; then
    MEILISEARCH_MODELS_CSV='App\Models\Artwork,App\Models\User,App\Models\Group,App\Models\Post,App\Models\Message'
  fi

  IFS=',' read -r -a meilisearch_models <<< "$MEILISEARCH_MODELS_CSV"

  echo "Importing searchable models into Meilisearch (auto-creates indexes)..."
  for model in "${meilisearch_models[@]}"; do
    [[ -n "$model" ]] || continue
    echo "  -> $model"
    "$PHP_BIN" artisan scout:import "$model"
  done
  echo "Syncing Meilisearch index settings..."
  "$PHP_BIN" artisan scout:sync-index-settings
  echo "Meilisearch setup complete."
fi

if [[ "$SKIP_MAINTENANCE" -eq 0 ]]; then
  "$PHP_BIN" artisan up
  trap - EXIT
fi
EOF

echo "Deployment complete."