#!/usr/bin/env bash
# Full Calendar platform deploy — SVN update, build, PM2, Apache, smoke tests.
#
# Usage (production server):
#   export SVN_USERNAME=your_username
#   sudo bash scripts/deploy.sh --svn
#
# SVN: deploy only runs `svn update` on an existing working copy (--svn).
#       One-time checkout: bash scripts/svn-checkout.sh
#
# Local / already-updated tree:
#   bash scripts/deploy.sh
#
# Options:
#   --svn            Run svn update before build (existing working copy only)
#   --skip-svn       Skip SVN even with --svn or SVN_USERNAME
#   --skip-build     Skip Node + Angular builds (PM2/Apache restart only)
#   --skip-node      Skip Node API build only
#   --skip-angular   Skip Angular build only
#   --skip-pm2       Build only, no PM2/Apache
#   --no-apache      PM2 only, skip Apache configure
#   --skip-health    Skip smoke tests
#   -h, --help       Show this help
#
# Env (alternative to flags):
#   SVN_URL, SVN_PASS_FILE, SKIP_SVN, SKIP_BUILD, SKIP_NODE_API, SKIP_ANGULAR
#   SKIP_PM2, SKIP_APACHE, SKIP_HEALTH
#   CALENDAR_SMOKE_USER, CALENDAR_SMOKE_EVENT, CALENDAR_APACHE_ROOT
#
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"

# shellcheck source=lib/curl-check.sh
source "$ROOT/scripts/lib/curl-check.sh"
# shellcheck source=lib/node-env-port.sh
source "$ROOT/scripts/lib/node-env-port.sh"
# shellcheck source=lib/load-env.sh
source "$ROOT/scripts/lib/load-env.sh"

DO_SVN=0
SKIP_SVN=0
SKIP_BUILD=0
SKIP_NODE=0
SKIP_ANGULAR=0
SKIP_PM2=0
NO_APACHE=0
SKIP_HEALTH=0
FAILURES=0

# SVN_URL="${SVN_URL:-http://newsvn.gventure.co.in/repos/gv_calender/trunk}"
SVN_URL="${SVN_URL:-http://newsvn.gventure.co.in/repos/gv_calender/rev/bugs_solved_18082026}"
SVN_USERNAME="vikas"
SVN_PASS_FILE="${SVN_PASS_FILE:-/etc/.svn-pass}"

log() { printf '\n==> %s\n' "$1"; }
warn() { printf 'WARNING: %s\n' "$1" >&2; }
fail() { printf 'FAIL: %s\n' "$1" >&2; FAILURES=$((FAILURES + 1)); }
die() { printf 'Error: %s\n' "$1" >&2; exit 1; }

usage() {
  sed -n '2,26p' "$0"
}

for arg in "$@"; do
  case "$arg" in
    --svn) DO_SVN=1 ;;
    --skip-svn) SKIP_SVN=1 ;;
    --skip-build) SKIP_BUILD=1 ;;
    --skip-node) SKIP_NODE=1 ;;
    --skip-angular) SKIP_ANGULAR=1 ;;
    --skip-pm2) SKIP_PM2=1 ;;
    --no-apache) NO_APACHE=1 ;;
    --skip-health) SKIP_HEALTH=1 ;;
    -h|--help) usage; exit 0 ;;
    *) die "Unknown option: $arg (try --help)" ;;
  esac
done

# Env aliases (SVN only when --svn is passed, not by default)
[[ "${SKIP_SVN:-}" == "1" ]] && { DO_SVN=0; SKIP_SVN=1; }
[[ "${SKIP_BUILD:-}" == "1" ]] && SKIP_BUILD=1
[[ "${SKIP_NODE_API:-}" == "1" ]] && SKIP_NODE=1
[[ "${SKIP_ANGULAR:-}" == "1" ]] && SKIP_ANGULAR=1
[[ "${SKIP_NODE:-}${SKIP_ANGULAR:-}" == "11" ]] && SKIP_BUILD=1
[[ "${SKIP_PM2:-}" == "1" ]] && SKIP_PM2=1
[[ "${SKIP_APACHE:-}" == "1" ]] && NO_APACHE=1
[[ "${SKIP_HEALTH:-}" == "1" ]] && SKIP_HEALTH=1

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "'$1' is required but not installed."
}

run_sudo() {
  if [[ "$(id -u)" -eq 0 ]]; then
    "$@"
  elif sudo -n true 2>/dev/null; then
    sudo "$@"
  else
    return 1
  fi
}

http_code() {
  curl_http_code "$1"
}

api_body_is_json() {
  local url=$1
  local expect=$2
  local body
  body="$(curl -sS --connect-timeout 10 --max-time 30 "$url" 2>/dev/null || true)"
  if [[ "$body" == *'<!doctype html'* || "$body" == *'<html'* ]]; then
    return 1
  fi
  [[ -n "$expect" && "$body" == *"$expect"* ]]
}

wait_for_health() {
  local url=$1
  local label=$2
  local max_attempts="${3:-15}"
  local attempt=1
  local code body

  while (( attempt <= max_attempts )); do
    code="$(http_code "$url")"
    body="$(curl -sS --connect-timeout 5 --max-time 10 "$url" 2>/dev/null || true)"
    if [[ "$code" == "200" && "$body" == *'"status":"ok"'* ]]; then
      printf '  OK  %s        HTTP %s  %s\n' "$label" "$code" "$url"
      return 0
    fi
    if (( attempt == 1 )); then
      printf '  … waiting for %s (%s)\n' "$label" "$url"
      if [[ "$code" == "404" ]]; then
        warn "HTTP 404 on $url — port may not be calendar-api (check: pm2 logs calendar-api --lines 40)"
      fi
    fi
    sleep 2
    attempt=$((attempt + 1))
  done

  fail "$label → HTTP $code ($url)"
  return 1
}

pm2_calendar_api_port() {
  pm2 jlist 2>/dev/null | node -e "
    let d=''; process.stdin.on('data',c=>d+=c);
    process.stdin.on('end',()=>{
      try {
        const a=JSON.parse(d).find(x=>x.name==='calendar-api');
        const port=a?.pm2_env?.env?.PORT;
        console.log(port||'');
      } catch { console.log(''); }
    });
  " 2>/dev/null || true
}

svn_read_password() {
  local pass=""
  if [[ -n "${SVN_PASSWORD:-}" ]]; then
    pass="$SVN_PASSWORD"
  elif [[ -r "$SVN_PASS_FILE" ]]; then
    pass="$(tr -d '\r\n' < "$SVN_PASS_FILE")"
  else
    die "SVN password required. Set SVN_PASSWORD, or create $SVN_PASS_FILE (chmod 600)."
  fi
  [[ -n "$pass" ]] || die "SVN password is empty in $SVN_PASS_FILE"
  printf '%s' "$pass"
}

svn_run() {
  local svn_pass
  svn_pass="$(svn_read_password)"
  svn "$@" \
    --username "$SVN_USERNAME" \
    --password "$svn_pass" \
    --non-interactive \
    --no-auth-cache
}

svn_verify_auth() {
  log "Verifying SVN credentials for $SVN_USERNAME"
  if ! svn_run ls "$SVN_URL" >/dev/null 2>&1; then
    die "SVN authentication failed for user '$SVN_USERNAME'. Fix $SVN_PASS_FILE (no trailing newline) or export SVN_PASSWORD."
  fi
}

svn_update() {
  require_cmd svn
  [[ -n "${SVN_USERNAME:-}" ]] || die "SVN_USERNAME is not set. Export it: export SVN_USERNAME=your_username"

  if [[ ! -d "$ROOT/.svn" ]]; then
    die "Not an SVN working copy ($ROOT). Run: bash scripts/svn-checkout.sh (one-time), or deploy with --skip-svn"
  fi

  svn_verify_auth

  log "SVN update ($SVN_URL)"
  svn_run update "$ROOT"
}

# ---------------------------------------------------------------------------
# 0. SVN
# ---------------------------------------------------------------------------
if [[ "$DO_SVN" -eq 1 && "$SKIP_SVN" -eq 0 ]]; then
  svn_update
else
  log "SVN update skipped (pass --svn to update from SVN)"
fi

chmod +x "$ROOT"/scripts/*.sh 2>/dev/null || true
mkdir -p "$ROOT/logs"

# ---------------------------------------------------------------------------
# 1b. Database migrations
# ---------------------------------------------------------------------------
log "Database migrations"
bash "$ROOT/scripts/run-db-migrations.sh"

# ---------------------------------------------------------------------------
# 1. Preflight
# ---------------------------------------------------------------------------
log "Preflight checks"

require_cmd node
require_cmd npm
require_cmd curl
[[ "$SKIP_PM2" -eq 0 ]] && require_cmd pm2

[[ -f "$ROOT/node/.env" ]] || die "node/.env not found. Copy node/.env.production.example → node/.env"

# shellcheck disable=SC1090
load_env_file "$ROOT/node/.env"

IS_PRODUCTION=0
[[ "${NODE_ENV:-development}" == "production" ]] && IS_PRODUCTION=1

if [[ "$IS_PRODUCTION" -eq 1 ]]; then
  bash "$ROOT/scripts/check-production-env.sh" "$ROOT/node/.env"
  if [[ -z "${PORT:-}" ]]; then
    die "PORT is not set in node/.env. Use PORT=3671 for production (must match Apache Define CALENDAR_API)."
  fi
  if [[ "${PORT}" == "3000" ]]; then
    warn "PORT=3000 looks like a dev default — production uses PORT=3671 (see node/.env.production.example)"
  fi
  if [[ "$NO_APACHE" -eq 0 && "$SKIP_PM2" -eq 0 ]]; then
    export CALENDAR_APACHE_MODE="${CALENDAR_APACHE_MODE:-1}"
  fi
else
  warn "NODE_ENV is not production — running dev-style deploy (PM2 static servers enabled)."
fi

SMOKE_USER="${CALENDAR_SMOKE_USER:-vikas-gventure}"
SMOKE_EVENT="${CALENDAR_SMOKE_EVENT:-telecom-solution-development}"
PUBLIC_BASE="${APP_URL:-https://calendar.gventure.info}"
PUBLIC_BASE="${PUBLIC_BASE%/}"

# ---------------------------------------------------------------------------
# 2. Build
# ---------------------------------------------------------------------------
if [[ "$SKIP_BUILD" -eq 0 && "$SKIP_NODE" -eq 0 ]]; then
  log "Building Node API"
  (cd "$ROOT/node" && npm ci && npm run build)
elif [[ "$SKIP_NODE" -eq 1 ]]; then
  log "Node API build skipped"
else
  log "Node API build skipped (--skip-build)"
fi

if [[ "$SKIP_BUILD" -eq 0 && "$SKIP_ANGULAR" -eq 0 ]]; then
  log "Building Angular apps (production)"
  (cd "$ROOT/angular" && npm ci && npm run build:all)
elif [[ "$SKIP_ANGULAR" -eq 1 ]]; then
  log "Angular build skipped"
else
  log "Angular build skipped (--skip-build)"
fi

log "Verifying Angular build output"
for app in host-dashboard public-booking embed-widget; do
  idx="$ROOT/angular/dist/$app/browser/index.html"
  [[ -f "$idx" ]] || die "Missing build output: $idx (run without --skip-build)"
  printf '  OK  %s\n' "$idx"
done

# ---------------------------------------------------------------------------
# 3. PM2 (optional)
# ---------------------------------------------------------------------------
if [[ "$SKIP_PM2" -eq 1 ]]; then
  log "PM2 deploy skipped (--skip-pm2)"
  printf '\nBuild finished. Start services: bash scripts/deploy.sh --skip-build --skip-svn\n'
  exit 0
fi

log "Allocating ports"
bash "$ROOT/scripts/allocate-ports.sh"
# shellcheck disable=SC1090
source "$ROOT/logs/ports.env"

export CALENDAR_API_PORT="$PORT"

log "Starting PM2 processes"
export CALENDAR_API_PORT CALENDAR_APACHE_MODE
if pm2 describe calendar-api >/dev/null 2>&1; then
  pm2 reload "$ROOT/ecosystem.config.cjs" --update-env
else
  pm2 start "$ROOT/ecosystem.config.cjs"
fi
pm2 save

if [[ "${CALENDAR_APACHE_MODE:-0}" == "1" ]]; then
  for app in calendar-host calendar-public calendar-embed; do
    pm2 describe "$app" >/dev/null 2>&1 && pm2 delete "$app" >/dev/null 2>&1 || true
  done
  pm2 save
fi

log "PM2 process status"
for app in calendar-api calendar-worker calendar-scheduler; do
  if pm2 describe "$app" >/dev/null 2>&1; then
    st="$(pm2 jlist 2>/dev/null | node -e "
      let d=''; process.stdin.on('data',c=>d+=c);
      process.stdin.on('end',()=>{
        try {
          const a=JSON.parse(d).find(x=>x.name==='$app');
          console.log(a?.pm2_env?.status||'unknown');
        } catch { console.log('unknown'); }
      });
    " 2>/dev/null || echo unknown)"
    if [[ "$st" == "online" ]]; then
      printf '  OK  %s (%s)\n' "$app" "$st"
    else
      fail "$app is not online (status: $st) — run: pm2 logs $app --lines 50"
    fi
    if [[ "$app" == "calendar-api" && "$st" == "online" ]]; then
      restarts="$(pm2 jlist 2>/dev/null | node -e "
        let d=''; process.stdin.on('data',c=>d+=c);
        process.stdin.on('end',()=>{
          try {
            const a=JSON.parse(d).find(x=>x.name==='calendar-api');
            console.log(a?.pm2_env?.restart_time??0);
          } catch { console.log(0); }
        });
      " 2>/dev/null || echo 0)"
      if [[ "${restarts:-0}" -gt 2 ]]; then
        warn "calendar-api has restarted ${restarts} times — check: pm2 logs calendar-api --lines 80"
        api_err_log="$ROOT/logs/calendar-api-error.log"
        if [[ -f "$api_err_log" ]]; then
          printf '  Last API errors (%s):\n' "$api_err_log" >&2
          tail -8 "$api_err_log" | sed 's/^/    /' >&2
        fi
        if [[ "${restarts:-0}" -gt 100 ]]; then
          warn "After fixing the crash cause, reset counter: pm2 reset calendar-api"
        fi
      fi
    fi
  else
    fail "$app is not registered in PM2"
  fi
done

# ---------------------------------------------------------------------------
# 4. Apache (production)
# ---------------------------------------------------------------------------
APACHE_CONFIGURED=0
if [[ "${CALENDAR_APACHE_MODE:-0}" == "1" && "$NO_APACHE" -eq 0 ]]; then
  log "Configuring Apache"
  if run_sudo bash "$ROOT/scripts/configure-apache.sh"; then
    APACHE_CONFIGURED=1
    printf '  OK  Apache vhost + API proxy updated\n'
  elif command -v apache2 >/dev/null 2>&1 || command -v apachectl >/dev/null 2>&1; then
    fail "Apache is installed but deploy could not configure it (need sudo)."
    warn "Run: sudo bash scripts/deploy.sh --svn --skip-build"
  else
    warn "Apache not found on this machine — skipped (PM2-only deploy)."
  fi
fi

# ---------------------------------------------------------------------------
# 5. Smoke tests
# ---------------------------------------------------------------------------
API_PORT="${PORT:?PORT must be set in node/.env}"
PM2_API_PORT="$(pm2_calendar_api_port)"
if [[ -n "$PM2_API_PORT" && "$PM2_API_PORT" != "$API_PORT" ]]; then
  warn "PM2 calendar-api PORT=$PM2_API_PORT differs from node/.env PORT=$API_PORT"
fi
LOCAL_API="http://127.0.0.1:${API_PORT}"

if [[ "$SKIP_HEALTH" -eq 0 ]]; then
  log "Smoke tests"

  wait_for_health "${LOCAL_API}/v1/health" "Local API health"

  code="$(http_code "${LOCAL_API}/v1/public/${SMOKE_USER}/${SMOKE_EVENT}")"
  if [[ "$code" == "200" ]]; then
    printf '  OK  Local public API        HTTP %s\n' "$code"
  else
    fail "Local public API → HTTP $code (/v1/public/${SMOKE_USER}/${SMOKE_EVENT})"
  fi

  if [[ "${CALENDAR_APACHE_MODE:-0}" == "1" && "$NO_APACHE" -eq 0 ]]; then
    APACHE_VHOST="${CALENDAR_APACHE_VHOST:-/etc/apache2/sites-available/calendar.gventure.info-le-ssl.conf}"
    APACHE_HOST="${PUBLIC_BASE#https://}"
    APACHE_HOST="${APACHE_HOST#http://}"
    APACHE_HOST="${APACHE_HOST%%/*}"

    apache_port=""
    if [[ -f "$APACHE_VHOST" ]]; then
      apache_port="$(grep -E 'ProxyPass\s+/api/v1/' "$APACHE_VHOST" 2>/dev/null \
        | grep -v '!' | grep -oE '127\.0\.0\.1:[0-9]+' | head -1 | cut -d: -f2 || true)"
      if [[ -z "$apache_port" ]]; then
        apache_port="$(grep -E 'Define CALENDAR_API' "$APACHE_VHOST" 2>/dev/null \
          | grep -oE '127\.0\.0\.1:[0-9]+' | head -1 | cut -d: -f2 || true)"
      fi
    fi
    if [[ -n "$apache_port" && "$apache_port" == "$API_PORT" ]]; then
      printf '  OK  Apache proxy port       %s (matches PM2)\n' "$apache_port"
    elif [[ -n "$apache_port" ]]; then
      fail "Apache proxy port $apache_port ≠ PM2 port $API_PORT in $APACHE_VHOST — run: sudo bash scripts/configure-apache.sh"
    else
      fail "Apache ProxyPass /api/v1/ not found in $APACHE_VHOST — run: sudo bash scripts/configure-apache.sh"
    fi

    code="$(curl_vhost_https "${APACHE_HOST}" "/api/v1/health" 10)"
    if [[ "$code" == "200" ]]; then
      printf '  OK  Apache API proxy (local) HTTP %s  (--resolve %s:443:127.0.0.1)\n' "$code" "$APACHE_HOST"
    else
      fail "Apache API proxy (local) → HTTP $code — run: sudo bash scripts/configure-apache.sh"
    fi

    pub_health="${PUBLIC_BASE}/api/v1/health"
    code="$(http_code "$pub_health")"
    if [[ "$code" != "200" ]]; then
      # Retry once — API may still be settling after PM2 reload
      sleep 2
      code="$(http_code "$pub_health")"
    fi
    if [[ "$code" == "200" ]]; then
      if api_body_is_json "$pub_health" '"status":"ok"'; then
        printf '  OK  Public API (HTTPS)      HTTP %s  %s\n' "$code" "$pub_health"
      else
        fail "Public API returns HTML not JSON ($pub_health) — run: sudo bash scripts/configure-apache.sh (fix ProxyPass + SPA rewrite)"
      fi
    else
      pub_hint=""
      local_via="$(curl_vhost_https "${APACHE_HOST}" "/api/v1/health" 8)"
      if [[ "$local_via" == "200" ]]; then
        pub_hint=" — local Apache proxy OK; check DNS/CDN or purge edge cache"
      fi
      if command -v getent >/dev/null 2>&1; then
        resolved="$(getent ahosts "$APACHE_HOST" 2>/dev/null | awk '{print $1; exit}' || true)"
        local_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
        if [[ -n "$resolved" && -n "$local_ip" && "$resolved" != "$local_ip" && "$resolved" != "127.0.0.1" ]]; then
          pub_hint=" — DNS ${APACHE_HOST}→${resolved} but server is ${local_ip} (CDN/proxy?); local Apache proxy OK"
        fi
      fi
      fail "Public API → HTTP $code ($pub_health)${pub_hint}"
    fi

    pub_event="${PUBLIC_BASE}/api/v1/public/${SMOKE_USER}/${SMOKE_EVENT}"
    code="$(http_code "$pub_event")"
    if [[ "$code" == "200" ]]; then
      printf '  OK  Public booking API      HTTP %s\n' "$code"
    else
      fail "Public booking API → HTTP $code (local Apache proxy must pass first — run diagnose-apache-api-proxy.sh)"
    fi

    pub_page="${PUBLIC_BASE}/book/${SMOKE_USER}/${SMOKE_EVENT}"
    code="$(http_code "$pub_page")"
    if [[ "$code" == "200" ]]; then
      printf '  OK  Public booking page     HTTP %s  %s\n' "$code" "$pub_page"
    else
      fail "Public booking page → HTTP $code ($pub_page)"
    fi

    code="$(http_code "${PUBLIC_BASE}/")"
    if [[ "$code" == "200" ]]; then
      printf '  OK  Host dashboard          HTTP %s  %s/\n' "$code" "$PUBLIC_BASE"
    else
      fail "Host dashboard → HTTP $code"
    fi
  fi
else
  log "Smoke tests skipped (--skip-health)"
fi

# ---------------------------------------------------------------------------
# 6. Summary
# ---------------------------------------------------------------------------
log "Deploy summary"
pm2 status 2>/dev/null | grep -E 'calendar-|App name' || pm2 status

printf '\n'
if [[ "${CALENDAR_APACHE_MODE:-0}" == "1" ]]; then
  printf 'Production URLs:\n'
  printf '  Dashboard:      %s/\n' "$PUBLIC_BASE"
  printf '  Public booking: %s/book/%s/%s\n' "$PUBLIC_BASE" "$SMOKE_USER" "$SMOKE_EVENT"
  printf '  API:            %s/api/v1/\n' "$PUBLIC_BASE"
  printf '  API (local):    http://127.0.0.1:%s/v1/\n' "$API_PORT"
else
  printf 'Local URLs (PM2):\n'
  printf '  API:             http://localhost:%s\n' "$API_PORT"
  printf '  Host dashboard:  http://localhost:%s\n' "${CALENDAR_HOST_PORT:-4200}"
  printf '  Public booking:  http://localhost:%s\n' "${CALENDAR_PUBLIC_PORT:-4201}"
  printf '  Embed widget:    http://localhost:%s\n' "${CALENDAR_EMBED_PORT:-4202}"
fi

printf '\nUseful commands:\n'
printf '  pm2 status\n'
printf '  pm2 logs calendar-api --lines 80\n'
printf '  bash scripts/show-ports.sh\n'
printf '  bash scripts/check-api-health.sh %s %s\n' "$SMOKE_USER" "$SMOKE_EVENT"
printf '  bash scripts/check-apache-api-proxy.sh\n'
printf '  pm2 save && pm2 startup\n'

printf '\n'
if [[ "$FAILURES" -gt 0 ]]; then
  printf '%s check(s) failed.\n' "$FAILURES"
  if [[ "$APACHE_CONFIGURED" -eq 0 && "${CALENDAR_APACHE_MODE:-0}" == "1" && "$NO_APACHE" -eq 0 ]]; then
    printf '\nTo fix Apache API proxy, run:\n  sudo bash scripts/configure-apache.sh\n'
  fi
  exit 1
fi

printf 'All checks passed.\n'
if [[ "$APACHE_CONFIGURED" -eq 0 && "${CALENDAR_APACHE_MODE:-0}" == "1" && "$NO_APACHE" -eq 0 ]] \
   && { command -v apache2 >/dev/null 2>&1 || command -v apachectl >/dev/null 2>&1; }; then
  printf '\nNote: re-run with sudo to auto-configure Apache:\n  sudo bash scripts/deploy.sh --svn --skip-build\n'
fi
