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

DOWNLOAD_BASE_URL="${DOWNLOAD_BASE_URL:-https://codex.jvniu.com/tg-sales}"
DOMAIN=""
ADMIN_EMAIL=""
INSTALL_DIR=""
REGISTRY=""
OFFLINE_IMAGE_DIR=""
DOMAIN_HOST=""
AUTO_CADDY=0

usage() {
  cat <<'USAGE'
Usage:
  bash install.sh --domain https://t.example.com --admin-email admin@example.com --install-dir /opt/tg-sales-archive

Options:
  --domain             Required. Public HTTPS URL, for example https://t.example.com
  --admin-email        Required. Initial super_admin email.
  --install-dir        Required. Installation directory, for example /opt/tg-sales-archive
  --registry           Optional. Registry prefix, for example registry.example.com/tg-sales
  --offline-image-dir  Optional. Directory containing tg-sales-backend-phase10.tar and tg-sales-worker-phase10.tar
  --auto-caddy         Optional. Install/configure host Caddy for this domain and auto HTTPS.
  -h, --help           Show help.

Security:
  Passwords and keys are generated locally on this production server.
  Do not pass admin password on command line. The initial password is generated and shown once.
USAGE
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --domain)
      DOMAIN="${2:-}"
      shift 2
      ;;
    --domain=*)
      DOMAIN="${1#*=}"
      shift
      ;;
    --admin-email)
      ADMIN_EMAIL="${2:-}"
      shift 2
      ;;
    --admin-email=*)
      ADMIN_EMAIL="${1#*=}"
      shift
      ;;
    --install-dir)
      INSTALL_DIR="${2:-}"
      shift 2
      ;;
    --install-dir=*)
      INSTALL_DIR="${1#*=}"
      shift
      ;;
    --registry)
      REGISTRY="${2:-}"
      shift 2
      ;;
    --registry=*)
      REGISTRY="${1#*=}"
      shift
      ;;
    --offline-image-dir)
      OFFLINE_IMAGE_DIR="${2:-}"
      shift 2
      ;;
    --offline-image-dir=*)
      OFFLINE_IMAGE_DIR="${1#*=}"
      shift
      ;;
    --auto-caddy)
      AUTO_CADDY=1
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "Unknown argument: $1" >&2
      usage
      exit 1
      ;;
  esac
done

require_value() {
  local name="$1"
  local value="$2"
  if [[ -z "$value" ]]; then
    echo "Missing required argument: $name" >&2
    usage
    exit 1
  fi
}

require_command() {
  local command="$1"
  if ! command -v "$command" >/dev/null 2>&1; then
    echo "Required command not found: $command" >&2
    exit 1
  fi
}

require_value "--domain" "$DOMAIN"
require_value "--admin-email" "$ADMIN_EMAIL"
require_value "--install-dir" "$INSTALL_DIR"

extract_domain_host() {
  local raw="$1"
  raw="${raw#http://}"
  raw="${raw#https://}"
  raw="${raw%%/*}"
  raw="${raw%%:*}"
  printf '%s' "$raw"
}

DOMAIN_HOST="$(extract_domain_host "$DOMAIN")"
if [[ -z "$DOMAIN_HOST" ]]; then
  echo "Unable to parse domain host from --domain: $DOMAIN" >&2
  exit 1
fi

require_command curl
require_command docker
require_command openssl

if ! docker info >/dev/null 2>&1; then
  echo "Docker is not running or current user cannot access Docker." >&2
  exit 1
fi

if ! docker compose version >/dev/null 2>&1; then
  echo "Docker Compose plugin is required: docker compose version failed." >&2
  exit 1
fi

umask 077
mkdir -p "$INSTALL_DIR"
cd "$INSTALL_DIR"
echo "Installing Telegram sales archive to $INSTALL_DIR"
echo "Download base: $DOWNLOAD_BASE_URL"
echo "Domain: $DOMAIN"

COMPOSE_FILE="$INSTALL_DIR/docker-compose.prod.yml"
ENV_EXAMPLE="$INSTALL_DIR/.env.production.example"
ENV_FILE="$INSTALL_DIR/.env"
IMAGE_CACHE_DIR="$INSTALL_DIR/image-cache"
COMPOSE=(docker compose -f "$COMPOSE_FILE")

download_file() {
  local name="$1"
  local target="$2"
  local tmp
  tmp="$(mktemp)"
  echo "Downloading $name ..."
  curl -fL --progress-bar --retry 3 --retry-delay 2 "$DOWNLOAD_BASE_URL/$name" -o "$tmp"
  mv "$tmp" "$target"
}

set_env() {
  local key="$1"
  local value="$2"
  local tmp
  tmp="$(mktemp)"
  if [[ -f "$ENV_FILE" ]] && grep -qE "^${key}=" "$ENV_FILE"; then
    awk -v key="$key" -v value="$value" '
      BEGIN { done = 0 }
      $0 ~ "^" key "=" { print key "=" value; done = 1; next }
      { print }
      END { if (done == 0) print key "=" value }
    ' "$ENV_FILE" > "$tmp"
  else
    if [[ -f "$ENV_FILE" ]]; then
      cp "$ENV_FILE" "$tmp"
    fi
    echo "${key}=${value}" >> "$tmp"
  fi
  mv "$tmp" "$ENV_FILE"
}

get_env() {
  local key="$1"
  if [[ ! -f "$ENV_FILE" ]]; then
    return 0
  fi
  grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d= -f2- | sed -e 's/^"//' -e 's/"$//'
}

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

generate_app_key() {
  printf 'base64:%s' "$(openssl rand -base64 32)"
}

ensure_secret() {
  local key="$1"
  local value
  value="$(get_env "$key" || true)"
  if [[ -z "$value" ]]; then
    set_env "$key" "$(random_hex 32)"
  fi
}

install_caddy_if_missing() {
  if command -v caddy >/dev/null 2>&1; then
    return 0
  fi

  echo "Caddy is not installed. Installing Caddy on this server..."
  if command -v apt-get >/dev/null 2>&1; then
    export DEBIAN_FRONTEND=noninteractive
    apt-get update
    apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl gnupg
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
      gpg --dearmor --yes -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
      -o /etc/apt/sources.list.d/caddy-stable.list
    chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    chmod o+r /etc/apt/sources.list.d/caddy-stable.list
    apt-get update
    apt-get install -y caddy
  elif command -v dnf >/dev/null 2>&1; then
    dnf install -y dnf-plugins-core || dnf install -y dnf5-plugins
    dnf copr enable -y @caddy/caddy
    dnf install -y caddy
  else
    echo "Unable to install Caddy automatically: apt-get or dnf was not found." >&2
    exit 1
  fi

  if ! command -v caddy >/dev/null 2>&1; then
    echo "Caddy installation finished but caddy command was not found." >&2
    exit 1
  fi
}

configure_caddy() {
  local caddy_dir="/etc/caddy"
  local caddyfile="$caddy_dir/Caddyfile"
  local backup_file tmp start_marker end_marker

  install_caddy_if_missing

  mkdir -p "$caddy_dir"
  if [[ -f "$caddyfile" ]]; then
    backup_file="$caddyfile.bak.$(date +%Y%m%d_%H%M%S)"
    cp "$caddyfile" "$backup_file"
    echo "Backed up existing Caddyfile to $backup_file"
  fi

  tmp="$(mktemp)"
  start_marker="# tg-sales-archive managed block start"
  end_marker="# tg-sales-archive managed block end"

  if [[ -f "$caddyfile" ]]; then
    awk -v start="$start_marker" -v end="$end_marker" '
      $0 == start { skip = 1; next }
      $0 == end { skip = 0; next }
      skip != 1 { print }
    ' "$caddyfile" > "$tmp"
  fi

  {
    printf '\n%s\n' "$start_marker"
    printf '%s {\n' "$DOMAIN_HOST"
    printf '    reverse_proxy 127.0.0.1:8080\n'
    printf '}\n'
    printf '%s\n' "$end_marker"
  } >> "$tmp"

  mv "$tmp" "$caddyfile"
  chmod 644 "$caddyfile"

  caddy fmt --overwrite "$caddyfile" >/dev/null
  caddy validate --config "$caddyfile"

  if command -v systemctl >/dev/null 2>&1 && \
    systemctl list-unit-files caddy.service --no-legend 2>/dev/null | grep -q '^caddy.service'; then
    systemctl enable caddy >/dev/null 2>&1 || true
    if systemctl is-active --quiet caddy; then
      systemctl reload caddy || systemctl restart caddy
    else
      systemctl restart caddy
    fi
  else
    caddy reload --config "$caddyfile"
  fi

  echo "[OK] Caddy reverse proxy configured for $DOMAIN_HOST -> 127.0.0.1:8080"
}

check_public_login() {
  local url="${DOMAIN%/}/login"
  local attempt http_code

  echo "Checking public login endpoint: curl -I $url"
  for attempt in $(seq 1 18); do
    http_code="$(curl -sS -o /dev/null -I -w '%{http_code}' --max-time 12 "$url" 2>/dev/null || true)"
    if [[ "$http_code" == "200" || "$http_code" == "302" ]]; then
      echo "[OK] Public login page: HTTP $http_code"
      return 0
    fi
    sleep 5
  done

  echo "[WARN] Public HTTPS login check did not pass yet. Check DNS, ports 80/443, Caddy logs and certificate issuance."
  return 0
}

download_file docker-compose.prod.yml "$COMPOSE_FILE"
download_file .env.production.example "$ENV_EXAMPLE"
download_file upgrade.sh "$INSTALL_DIR/upgrade.sh"
download_file backup.sh "$INSTALL_DIR/backup.sh"
download_file healthcheck.sh "$INSTALL_DIR/healthcheck.sh"
chmod +x "$INSTALL_DIR/upgrade.sh" "$INSTALL_DIR/backup.sh" "$INSTALL_DIR/healthcheck.sh"

NEW_ENV=0
GENERATED_ADMIN_PASSWORD=""
if [[ ! -f "$ENV_FILE" ]]; then
  cp "$ENV_EXAMPLE" "$ENV_FILE"
  NEW_ENV=1
fi

set_env APP_ENV production
set_env APP_DEBUG false
set_env APP_URL "$DOMAIN"
set_env ADMIN_DEFAULT_EMAIL "$ADMIN_EMAIL"
set_env DB_DATABASE tg_sales_archive
set_env DB_USERNAME tg_sales_user
set_env MYSQL_DATABASE tg_sales_archive
set_env MYSQL_USER tg_sales_user

if [[ -n "$REGISTRY" ]]; then
  REGISTRY="${REGISTRY%/}"
  set_env BACKEND_IMAGE "$REGISTRY/backend:phase10"
  set_env WORKER_IMAGE "$REGISTRY/worker:phase10"
else
  set_env BACKEND_IMAGE tg-sales-backend:phase10
  set_env WORKER_IMAGE tg-sales-worker:phase10
fi

if [[ -z "$(get_env APP_KEY || true)" ]]; then
  set_env APP_KEY "$(generate_app_key)"
fi

DB_PASSWORD_VALUE="$(get_env DB_PASSWORD || true)"
MYSQL_PASSWORD_VALUE="$(get_env MYSQL_PASSWORD || true)"
if [[ -z "$DB_PASSWORD_VALUE" && -z "$MYSQL_PASSWORD_VALUE" ]]; then
  DB_PASSWORD_VALUE="$(random_hex 24)"
elif [[ -z "$DB_PASSWORD_VALUE" ]]; then
  DB_PASSWORD_VALUE="$MYSQL_PASSWORD_VALUE"
fi
set_env DB_PASSWORD "$DB_PASSWORD_VALUE"
set_env MYSQL_PASSWORD "$DB_PASSWORD_VALUE"

ensure_secret MYSQL_ROOT_PASSWORD
ensure_secret MINIO_ACCESS_KEY
ensure_secret MINIO_SECRET_KEY

INTERNAL_TOKEN_VALUE="$(get_env INTERNAL_TOKEN || true)"
if [[ -z "$INTERNAL_TOKEN_VALUE" ]]; then
  INTERNAL_TOKEN_VALUE="$(random_hex 32)"
fi
INTERNAL_SECRET_VALUE="$(get_env INTERNAL_SECRET || true)"
if [[ -z "$INTERNAL_SECRET_VALUE" ]]; then
  INTERNAL_SECRET_VALUE="$(random_hex 32)"
fi
set_env INTERNAL_TOKEN "$INTERNAL_TOKEN_VALUE"
set_env INTERNAL_API_TOKEN "$INTERNAL_TOKEN_VALUE"
set_env INTERNAL_SECRET "$INTERNAL_SECRET_VALUE"
set_env INTERNAL_API_SECRET "$INTERNAL_SECRET_VALUE"

if [[ -z "$(get_env ADMIN_DEFAULT_PASSWORD || true)" ]]; then
  GENERATED_ADMIN_PASSWORD="$(random_hex 20)"
  set_env ADMIN_DEFAULT_PASSWORD "$GENERATED_ADMIN_PASSWORD"
fi

chmod 600 "$ENV_FILE"

load_or_pull_images() {
  if [[ -n "$REGISTRY" ]]; then
    "${COMPOSE[@]}" pull
    return
  fi

  local source_dir="$OFFLINE_IMAGE_DIR"
  if [[ -z "$source_dir" ]]; then
    mkdir -p "$IMAGE_CACHE_DIR"
    download_file tg-sales-backend-phase10.tar "$IMAGE_CACHE_DIR/tg-sales-backend-phase10.tar"
    download_file tg-sales-worker-phase10.tar "$IMAGE_CACHE_DIR/tg-sales-worker-phase10.tar"
    source_dir="$IMAGE_CACHE_DIR"
  fi

  echo "Loading backend image from $source_dir/tg-sales-backend-phase10.tar ..."
  docker load -i "$source_dir/tg-sales-backend-phase10.tar"
  echo "Loading worker image from $source_dir/tg-sales-worker-phase10.tar ..."
  docker load -i "$source_dir/tg-sales-worker-phase10.tar"
}

wait_for_healthy() {
  local service="$1"
  local timeout="${2:-180}"
  local start
  start="$(date +%s)"

  while true; do
    local container_id status now
    container_id="$("${COMPOSE[@]}" ps -q "$service" || true)"
    if [[ -n "$container_id" ]]; then
      status="$(docker inspect --format '{{ if .State.Health }}{{ .State.Health.Status }}{{ else }}none{{ end }}' "$container_id" 2>/dev/null || true)"
      if [[ "$status" == "healthy" ]]; then
        echo "[OK] $service healthy"
        return 0
      fi
    fi

    now="$(date +%s)"
    if (( now - start > timeout )); then
      echo "Timed out waiting for $service to become healthy." >&2
      "${COMPOSE[@]}" ps
      exit 1
    fi
    sleep 3
  done
}

load_or_pull_images

"${COMPOSE[@]}" config >/dev/null
"${COMPOSE[@]}" run --rm --no-deps --entrypoint true tg-worker-sales01 >/dev/null
echo "Starting mysql, redis, minio, backend and scheduler ..."
"${COMPOSE[@]}" up -d mysql redis minio minio-init backend backend-scheduler

wait_for_healthy mysql 240
wait_for_healthy redis 180

"${COMPOSE[@]}" exec -T backend php artisan migrate --seed --force
"${COMPOSE[@]}" exec -T backend php artisan optimize:clear
"${COMPOSE[@]}" exec -T backend php artisan config:cache

"$INSTALL_DIR/healthcheck.sh" --install-dir "$INSTALL_DIR"
if [[ "$AUTO_CADDY" == "1" ]]; then
  configure_caddy
  check_public_login
else
  echo "[INFO] Caddy auto binding skipped. Pass --auto-caddy to install/configure Caddy automatically."
fi

echo
echo "Installation finished."
echo "Backend URL: $DOMAIN"
echo "Local reverse proxy target: http://127.0.0.1:8080"
if [[ "$AUTO_CADDY" == "1" ]]; then
  echo "Caddyfile: /etc/caddy/Caddyfile"
fi
echo "Admin email: $ADMIN_EMAIL"
if [[ -n "$GENERATED_ADMIN_PASSWORD" ]]; then
  echo "Initial admin password: $GENERATED_ADMIN_PASSWORD"
else
  echo "Initial admin password: already exists in local .env; not displayed on repeat install."
fi
echo ".env path: $ENV_FILE"
echo
echo "Important:"
echo "- The initial admin password is shown only during first install. Log in and change it immediately."
echo "- .env contains production secrets. Keep it local, chmod 600, and encrypt backups."
if [[ "$AUTO_CADDY" == "1" ]]; then
  echo "- Caddy has been configured for $DOMAIN_HOST and proxies to http://127.0.0.1:8080."
else
  echo "- Configure your reverse proxy to http://127.0.0.1:8080, or rerun install.sh with --auto-caddy."
fi
echo "- To change domain later, update APP_URL in .env and your reverse proxy config; no image rebuild is needed."
echo "- Do not run docker compose down -v."
