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

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
MANIFEST="$REPO_ROOT/assets/manifest.yml"
SSH_USER="grenade"
REMOTE_STAGE="/tmp/swym-deploy"

declare -A SVC_TO_BIN=(
  [swym-api]=swym-api
  [swym-ingest-binance]=swym-ingest-binance
  [swym-market-worker]=market-worker
  [swym-paper-executor]=paper-executor
)
declare -A SVC_TO_CFG=(
  [swym-api]=api.json
  [swym-ingest-binance]=ingest_binance.json
  [swym-market-worker]=market_worker.json
  [swym-paper-executor]=paper_executor.json
)

# Deploy the dashboard dist folder and nginx config to a single host.
deploy_dashboard() {
  local env="$1"
  local host webroot nginx_conf nginx_site
  host="$(yq --raw-output --arg env "$env" '.environments[$env]["swym-dashboard"].host' "$MANIFEST")"
  webroot="$(yq --raw-output --arg env "$env" '.environments[$env]["swym-dashboard"].webroot' "$MANIFEST")"
  nginx_conf="$(yq --raw-output --arg env "$env" '.environments[$env]["swym-dashboard"].nginx_conf' "$MANIFEST")"
  nginx_site="$(yq --raw-output --arg env "$env" '.environments[$env]["swym-dashboard"].nginx_site' "$MANIFEST")"

  if [[ "$host" == "null" ]]; then
    return 0  # no dashboard defined for this env
  fi

  echo "==> [$host] Deploying dashboard..."

  # Build the dashboard
  echo "    Building dashboard..."
  (cd "$REPO_ROOT/dashboard" && pnpm install && pnpm build)

  # Transfer dist to webroot
  echo "    rsync dist -> $SSH_USER@$host:$webroot"
  ssh "$SSH_USER@$host" "sudo mkdir -p '$webroot'"
  rsync -az --delete "$REPO_ROOT/dashboard/dist/" "$SSH_USER@$host:/tmp/swym-dashboard-dist/"

  # Transfer nginx config to a temp location
  rsync -az "$REPO_ROOT/$nginx_conf" "$SSH_USER@$host:/tmp/swym-nginx-site.conf"

  # Install files and configure nginx remotely
  # shellcheck disable=SC2029
  ssh "$SSH_USER@$host" bash -s -- "$webroot" "$nginx_site" <<'REMOTE'
set -euo pipefail
WEBROOT="$1"
NGINX_SITE="$2"
SITES_AVAILABLE="/etc/nginx/sites-available/$NGINX_SITE"
SITES_ENABLED="/etc/nginx/sites-enabled/$NGINX_SITE"

echo "  Installing dashboard files to $WEBROOT..."
sudo rsync -a --delete --chown nginx:nginx /tmp/swym-dashboard-dist/ "$WEBROOT/"
sudo semanage fcontext -a -t httpd_sys_content_t "$WEBROOT(/.*)?"
sudo restorecon -Rv $WEBROOT
rm -rf /tmp/swym-dashboard-dist

echo "  Installing nginx config to $SITES_AVAILABLE..."
sudo install -m 644 /tmp/swym-nginx-site.conf "$SITES_AVAILABLE"
rm -f /tmp/swym-nginx-site.conf

echo "  Enabling nginx site..."
sudo ln -sf "$SITES_AVAILABLE" "$SITES_ENABLED"

echo "  Reloading nginx..."
sudo nginx -t
sudo systemctl reload nginx.service

echo "  Dashboard deployed."
REMOTE

  echo "==> [$host] Dashboard done."
}

usage() {
  echo "Usage: $0 <env> [drop]"
  echo "  env:  dev | stage | prod"
  echo "  drop: drop and recreate the public schema before deploying"
  exit 1
}

resolve_config() {
  local env="$1" cfg="$2"
  local overlay="$REPO_ROOT/config/overlays/$env/$cfg"
  if [[ -f "$overlay" ]]; then
    echo "$overlay"
  else
    echo "$REPO_ROOT/config/$env/$cfg"
  fi
}

# Returns the list of hosts for a given service in the given environment.
# swym-api uses a scalar 'host' key; worker services use a 'hosts' list.
get_hosts() {
  local env="$1" svc="$2"
  if [[ "$svc" == "swym-api" ]]; then
    yq --raw-output --arg env "${env}" --arg svc "${svc}" '.environments.[$env].[$svc].host' "$MANIFEST"
  else
    yq --raw-output --arg env "${env}" --arg svc "${svc}" '.environments.[$env].[$svc].hosts[]' "$MANIFEST"
  fi
}

deploy_to_host() {
  local host="$1"
  shift
  local services=("$@")

  echo "==> [$host] Deploying services: ${services[*]}"

  # Collect the binaries, configs, and unit files needed for this host
  local bins=()
  local cfgs=()
  local units=()
  for svc in "${services[@]}"; do
    bins+=("${SVC_TO_BIN[$svc]}")
    cfgs+=("$(resolve_config "$ENV" "${SVC_TO_CFG[$svc]}")")
    units+=("$REPO_ROOT/assets/systemd/$svc.service")
  done

  # Stage files locally into a temp directory for a single rsync
  local local_stage
  local_stage="$(mktemp -d)"
  trap 'rm -rf "$local_stage"' RETURN

  mkdir -p "$local_stage/bin" "$local_stage/config" "$local_stage/systemd"

  for bin in "${bins[@]}"; do
    cp "$REPO_ROOT/target/release/$bin" "$local_stage/bin/"
  done
  for cfg in "${cfgs[@]}"; do
    cp "$cfg" "$local_stage/config/"
  done
  for unit in "${units[@]}"; do
    cp "$unit" "$local_stage/systemd/"
  done

  # Transfer to remote staging area
  echo "    rsync -> $SSH_USER@$host:$REMOTE_STAGE"
  rsync -az --delete "$local_stage/" "$SSH_USER@$host:$REMOTE_STAGE/"

  # Stop services, install, reload, start
  # shellcheck disable=SC2029
  ssh "$SSH_USER@$host" bash -s -- "${services[@]}" <<'REMOTE'
set -euo pipefail
STAGE=/tmp/swym-deploy
SERVICES=("$@")

echo "  Stopping services..."
for svc in "${SERVICES[@]}"; do
  sudo systemctl stop "$svc" 2>/dev/null || true
done

echo "  Installing binaries..."
sudo install -m 755 "$STAGE"/bin/* /usr/local/bin/

echo "  Installing configs..."
sudo mkdir -p /etc/swym
sudo install -m 644 "$STAGE"/config/* /etc/swym/

echo "  Installing systemd units..."
sudo install -m 644 "$STAGE"/systemd/* /etc/systemd/system/

echo "  Reloading systemd and starting services..."
sudo systemctl daemon-reload
for svc in "${SERVICES[@]}"; do
  sudo systemctl enable --now "$svc"
done

echo "  Service status:"
for svc in "${SERVICES[@]}"; do
  echo "  --- $svc ---"
  systemctl status --no-pager "$svc" || true
done

rm -rf "$STAGE"
REMOTE

  echo "==> [$host] Done."
}

# --- Main ---

[[ $# -ge 1 ]] || usage
ENV="$1"
DROP_DB="${2:-}"

# Check prerequisites
if ! command -v yq &>/dev/null; then
  echo "Error: 'yq' is required but not installed." >&2
  echo "  Install with: sudo dnf install yq   (or brew install yq on macOS)" >&2
  exit 1
fi

# Validate environment exists in manifest
if [[ "$(yq ".environments | has(\"$ENV\")" "$MANIFEST")" != "true" ]]; then
  echo "Error: environment '$ENV' not found in $MANIFEST" >&2
  exit 1
fi

# Optionally drop and recreate the public schema
if [[ "$DROP_DB" == "drop" ]] && [[ "$ENV" == "dev" ]]; then
  api_cfg="$(resolve_config "$ENV" "api.json")"
  config_db_url=$(jq -r '.database.url' "$api_cfg")
  db_user="$(echo ${config_db_url} | cut -d '/' -f 3 | cut -d '@' -f 1)"
  db_host="$(echo ${config_db_url} | cut -d '@' -f 2 | cut -d ':' -f 1)"
  db_port="$(echo ${config_db_url} | cut -d ':' -f 3 | cut -d '/' -f 1)"
  db_name="$(echo ${config_db_url} | cut -d '/' -f 4 | cut -d '?' -f 1)"
  db_ssl_mode=verify-full
  db_ssl_root_cert=/etc/pki/ca-trust/source/anchors/ca.internal-rsa.pem
  db_ssl_cert=/etc/pki/tls/misc/$(hostnamectl hostname)-rsa.pem
  db_ssl_key=/etc/pki/tls/private/$(hostnamectl hostname)-rsa.pem
  db_url="postgres://${db_user}@${db_host}:${db_port}/${db_name}?sslmode=${db_ssl_mode}&sslrootcert=${db_ssl_root_cert}&sslcert=${db_ssl_cert}&sslkey=${db_ssl_key}"

  echo "==> Dropping all objects in database ($ENV, ${db_url})..."
  psql "${db_url}" -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
  echo "==> Done. All tables, types, and indexes have been dropped."
fi

# Build host -> services mapping
declare -A HOST_SERVICES  # host -> space-separated list of services

api_host="$(get_hosts "$ENV" swym-api)"
HOST_SERVICES["$api_host"]="${HOST_SERVICES["$api_host"]+${HOST_SERVICES["$api_host"]} }swym-api"

for svc in swym-ingest-binance swym-market-worker swym-paper-executor; do
  while IFS= read -r host; do
    HOST_SERVICES["$host"]="${HOST_SERVICES["$host"]+${HOST_SERVICES["$host"]} }$svc"
  done < <(get_hosts "$ENV" "$svc")
done

# Collect all unique hosts (services + optional dashboard)
declare -A ALL_HOSTS
for host in "${!HOST_SERVICES[@]}"; do
  ALL_HOSTS["$host"]=1
done
dashboard_host="$(yq --raw-output --arg env "$ENV" '.environments[$env]["swym-dashboard"].host // "null"' "$MANIFEST")"
if [[ "$dashboard_host" != "null" ]]; then
  ALL_HOSTS["$dashboard_host"]=1
fi

# SSH connectivity pre-check
echo "==> Checking SSH connectivity..."
all_ok=true
for host in "${!ALL_HOSTS[@]}"; do
  if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "$SSH_USER@$host" true 2>/dev/null; then
    echo "  ERROR: cannot reach $SSH_USER@$host" >&2
    all_ok=false
  else
    echo "  OK: $host"
  fi
done
if [[ "$all_ok" != "true" ]]; then
  echo "Error: one or more hosts are unreachable; aborting." >&2
  exit 1
fi

# Build Rust services
echo "==> Building release binaries..."
cargo build --release --manifest-path "$REPO_ROOT/Cargo.toml"

# Deploy Rust services to each host
failed_hosts=()
for host in "${!HOST_SERVICES[@]}"; do
  IFS=' ' read -ra svcs <<< "${HOST_SERVICES[$host]}"
  if ! deploy_to_host "$host" "${svcs[@]}"; then
    echo "ERROR: deployment to $host failed" >&2
    failed_hosts+=("$host")
  fi
done

# Deploy dashboard (builds dashboard internally)
if [[ "$dashboard_host" != "null" ]]; then
  if ! deploy_dashboard "$ENV"; then
    echo "ERROR: dashboard deployment failed" >&2
    failed_hosts+=("dashboard:$dashboard_host")
  fi
fi

# Summary
if [[ ${#failed_hosts[@]} -gt 0 ]]; then
  echo ""
  echo "==> FAILED hosts: ${failed_hosts[*]}" >&2
  exit 1
fi

echo ""
echo "==> Deployment to '$ENV' complete."
