#!/usr/bin/env bash

set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${ROOT_DIR}"

source "${ROOT_DIR}/scripts/docker-prod-common.sh"

usage() {
  cat <<'EOF'
Usage:
  ./deploy-production.sh [--with-pgmanage] [--skip-prune] [--image-tag <tag>] [--fetch-through-vpn] [--pull-registry]

What it does:
  1. Validates .env.docker.production
  2. Loads IMAGE_TAG, APP_IMAGE, registry, and archive settings from the env file
  3. Loads a matching Docker image archive from IMAGE_ARCHIVE_DIR when present
  4. Otherwise logs in to the registry if credentials are configured, then pulls
  5. Reuses healthy Traefik, Postgres, and Redis containers when present
  6. Waits for health checks and prints container status

VPN/shared-folder image flow:
  Put an archive matching IMAGE_TAG in IMAGE_ARCHIVE_DIR before running this script.
  Example: /opt/docker-image-transfer/carmanagement-latest.tar
  When IMAGE_ARCHIVE_REQUIRED=true and VPN_CONTAINER is set, a missing archive is
  fetched through the VPN container before loading the release image.

Options:
  --image-tag <tag>  Override IMAGE_TAG from .env.docker.production for this run
  --fetch-through-vpn
                 Fetch IMAGE_TAG into IMAGE_ARCHIVE_DIR before deployment.
                 Requires VPN_CONTAINER and prompts for REGISTRY_TOKEN when unset.
  --pull-registry   Ignore IMAGE_ARCHIVE_DIR and pull directly from the registry
EOF
}

include_pgmanage=0
skip_prune=0
fetch_through_vpn=0

while [[ $# -gt 0 ]]; do
  case "$1" in
    --with-pgmanage)
      include_pgmanage=1
      shift
      ;;
    --skip-prune)
      skip_prune=1
      shift
      ;;
    --image-tag)
      if [[ -z "${2:-}" ]]; then
        echo "--image-tag requires a tag value." >&2
        usage >&2
        exit 1
      fi
      IMAGE_TAG="$2"
      export IMAGE_TAG
      shift 2
      ;;
    --fetch-through-vpn)
      fetch_through_vpn=1
      shift
      ;;
    --pull-registry)
      IMAGE_ARCHIVE_DIR=""
      IMAGE_ARCHIVE_REQUIRED=false
      IMAGE_ARCHIVE_DISABLED=true
      export IMAGE_ARCHIVE_DIR IMAGE_ARCHIVE_REQUIRED IMAGE_ARCHIVE_DISABLED
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "Unknown option: $1" >&2
      usage >&2
      exit 1
      ;;
  esac
done

if [[ "${fetch_through_vpn}" -eq 1 && ("${IMAGE_ARCHIVE_DISABLED:-false}" == "true" || "${IMAGE_ARCHIVE_DISABLED:-0}" == "1") ]]; then
  echo "--fetch-through-vpn and --pull-registry cannot be used together." >&2
  usage >&2
  exit 1
fi

require_command() {
  local command_name="$1"

  if ! command -v "${command_name}" >/dev/null 2>&1; then
    echo "Missing required command: ${command_name}" >&2
    exit 1
  fi
}

fetch_release_archive_through_vpn() {
  if ! should_fetch_release_archive_through_vpn; then
    return 0
  fi

  detect_vpn_container_if_unset

  if [[ -z "${VPN_CONTAINER:-}" ]]; then
    echo "VPN_CONTAINER is required to fetch the image archive through VPN." >&2
    usage >&2
    exit 1
  fi

  echo "Fetching image archive through VPN container"
  IMAGE_TAG="${IMAGE_TAG}" \
    APP_IMAGE="${APP_IMAGE:-10.0.0.4/melabidi/carmanagement}" \
    TRANSFER_DIR="${IMAGE_ARCHIVE_DIR:-/opt/docker-image-transfer}" \
    "${ROOT_DIR}/fetch-image-through-vpn.sh" "${IMAGE_TAG}"
}

detect_vpn_container_if_unset() {
  local candidates
  local candidate_count

  if [[ -n "${VPN_CONTAINER:-}" ]]; then
    return 0
  fi

  candidates="$(
    docker ps --format '{{.ID}} {{.Names}} {{.Image}}' 2>/dev/null \
      | awk 'BEGIN { IGNORECASE = 1 } /(vpn|gluetun|wireguard|openvpn|tailscale|wg-easy)/ { print $1 }'
  )"

  candidate_count="$(printf '%s\n' "${candidates}" | sed '/^$/d' | wc -l | tr -d ' ')"

  if [[ "${candidate_count}" == "1" ]]; then
    VPN_CONTAINER="$(printf '%s\n' "${candidates}" | sed '/^$/d' | sed -n '1p')"
    export VPN_CONTAINER
    echo "Auto-detected VPN container: ${VPN_CONTAINER}"
  fi
}

ensure_archive_source_ready() {
  if [[ "${IMAGE_ARCHIVE_DISABLED:-false}" == "true" || "${IMAGE_ARCHIVE_DISABLED:-0}" == "1" ]]; then
    return 0
  fi

  if [[ "${IMAGE_ARCHIVE_REQUIRED:-false}" != "true" && "${IMAGE_ARCHIVE_REQUIRED:-0}" != "1" ]]; then
    return 0
  fi

  if release_archive_exists; then
    return 0
  fi

  detect_vpn_container_if_unset

  if [[ -n "${VPN_CONTAINER:-}" ]]; then
    echo "No local image archive found; deploy will fetch it through VPN container ${VPN_CONTAINER}."
    return 0
  fi

  echo "IMAGE_ARCHIVE_REQUIRED=true and no matching archive exists in ${IMAGE_ARCHIVE_DIR:-<unset>}." >&2
  echo "Direct docker pull is disabled in this mode, and VPN_CONTAINER is not set." >&2
  echo "No single VPN container could be auto-detected from common VPN names/images." >&2
  echo "Find the VPN container with:" >&2
  echo "  docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}'" >&2
  echo "Run one of:" >&2
  echo "  VPN_CONTAINER=<vpn-container-id-or-name> ./deploy-production.sh --fetch-through-vpn" >&2
  echo "  VPN_CONTAINER=<vpn-container-id-or-name> ./fetch-image-through-vpn.sh ${IMAGE_TAG}" >&2
  echo "Or set VPN_CONTAINER in .env.docker.production and rerun ./deploy-production.sh." >&2
  exit 1
}

release_archive_exists() {
  local archive_dir="${IMAGE_ARCHIVE_DIR:-}"

  if [[ -z "${archive_dir}" || ! -d "${archive_dir}" ]]; then
    return 1
  fi

  shopt -s nullglob
  local archives=(
    "${archive_dir}"/*"${IMAGE_TAG}"*.tar
    "${archive_dir}"/*"${IMAGE_TAG}"*.tar.gz
    "${archive_dir}"/*"${IMAGE_TAG}"*.tgz
  )
  shopt -u nullglob

  [[ ${#archives[@]} -gt 0 ]]
}

should_fetch_release_archive_through_vpn() {
  if [[ "${IMAGE_ARCHIVE_DISABLED:-false}" == "true" || "${IMAGE_ARCHIVE_DISABLED:-0}" == "1" ]]; then
    return 1
  fi

  if [[ "${fetch_through_vpn}" -eq 1 ]]; then
    return 0
  fi

  if [[ "${IMAGE_ARCHIVE_REQUIRED:-false}" != "true" && "${IMAGE_ARCHIVE_REQUIRED:-0}" != "1" ]]; then
    return 1
  fi

  if [[ -z "${VPN_CONTAINER:-}" ]]; then
    return 1
  fi

  ! release_archive_exists
}

normalize_archive_dir() {
  if [[ -z "${IMAGE_ARCHIVE_DIR:-}" ]]; then
    return 0
  fi

  case "${IMAGE_ARCHIVE_DIR}" in
    /*)
      ;;
    *)
      IMAGE_ARCHIVE_DIR="${ROOT_DIR}/${IMAGE_ARCHIVE_DIR#./}"
      export IMAGE_ARCHIVE_DIR
      ;;
  esac
}

print_release_summary() {
  echo "Production deployment"
  echo "  Image: ${APP_IMAGE:-rentaldrivego/carmanagement}:${IMAGE_TAG}"
  echo "  Env: ${ENV_FILE}"
  echo "  Compose project: ${PROJECT_NAME}"

  if [[ "${IMAGE_ARCHIVE_DISABLED:-false}" == "true" || "${IMAGE_ARCHIVE_DISABLED:-0}" == "1" ]]; then
    echo "  Image source: registry pull (archive loading disabled)"
  elif [[ -n "${IMAGE_ARCHIVE_DIR:-}" ]]; then
    echo "  Image archive dir: ${IMAGE_ARCHIVE_DIR}"
  else
    echo "  Image source: registry pull"
  fi

  if [[ "${IMAGE_ARCHIVE_REQUIRED:-false}" == "true" || "${IMAGE_ARCHIVE_REQUIRED:-0}" == "1" ]]; then
    echo "  Registry pull: disabled (IMAGE_ARCHIVE_REQUIRED=${IMAGE_ARCHIVE_REQUIRED})"
  fi
}

container_status() {
  local container_name="$1"

  docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "${container_name}" 2>/dev/null || echo "missing"
}

compose_service_status() {
  local compose_name="$1"
  local service_name="$2"
  local container_id

  case "${compose_name}" in
    prod)
      container_id="$(prod_compose ps -q "${service_name}" 2>/dev/null || true)"
      ;;
    traefik)
      container_id="$(traefik_compose ps -q "${service_name}" 2>/dev/null || true)"
      ;;
    *)
      echo "missing"
      return 0
      ;;
  esac

  if [[ -z "${container_id}" ]]; then
    echo "missing"
    return 0
  fi

  docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "${container_id}" 2>/dev/null || echo "missing"
}

first_container_id() {
  docker ps -q "$@" 2>/dev/null | sed -n '1p'
}

ensure_container_on_traefik_network() {
  local container_id="$1"
  local networks

  networks="$(docker inspect --format='{{range $name, $_ := .NetworkSettings.Networks}}{{printf "%s " $name}}{{end}}' "${container_id}" 2>/dev/null || true)"
  case " ${networks} " in
    *" ${TRAEFIK_NETWORK} "*)
      ;;
    *)
      echo "Connecting existing Traefik container ${container_id} to ${TRAEFIK_NETWORK}."
      docker network connect "${TRAEFIK_NETWORK}" "${container_id}" 2>/dev/null || true
      ;;
  esac
}

find_running_traefik_container() {
  local container_id

  container_id="$(first_container_id --filter name=traefik)"
  if [[ -n "${container_id}" ]]; then
    printf '%s' "${container_id}"
    return 0
  fi

  container_id="$(first_container_id --filter ancestor=traefik --filter status=running)"
  if [[ -n "${container_id}" ]]; then
    printf '%s' "${container_id}"
    return 0
  fi

  container_id="$(first_container_id --filter ancestor=traefik:latest --filter status=running)"
  if [[ -n "${container_id}" ]]; then
    printf '%s' "${container_id}"
    return 0
  fi
}

ensure_ports_available_for_traefik() {
  local container_id
  local port_listener

  container_id="$(first_container_id --filter publish=80 --filter status=running)"
  if [[ -n "${container_id}" ]]; then
    echo "Port 80 is already used by container ${container_id}, and it does not look like Traefik." >&2
    echo "Stop that container or set up Traefik outside this deployment before rerunning." >&2
    exit 1
  fi

  port_listener="$(sudo ss -H -ltnp 'sport = :80' 2>/dev/null || ss -H -ltnp 'sport = :80' 2>/dev/null || true)"
  if [[ -n "${port_listener}" ]]; then
    echo "Port 80 is already used by a host process:" >&2
    echo "${port_listener}" >&2
    echo "Stop that process before starting Traefik, or manage Traefik outside this deployment." >&2
    exit 1
  fi

  container_id="$(first_container_id --filter publish=443 --filter status=running)"
  if [[ -n "${container_id}" ]]; then
    echo "Port 443 is already used by container ${container_id}, and it does not look like Traefik." >&2
    echo "Stop that container or set up Traefik outside this deployment before rerunning." >&2
    exit 1
  fi

  port_listener="$(sudo ss -H -ltnp 'sport = :443' 2>/dev/null || ss -H -ltnp 'sport = :443' 2>/dev/null || true)"
  if [[ -n "${port_listener}" ]]; then
    echo "Port 443 is already used by a host process:" >&2
    echo "${port_listener}" >&2
    echo "Stop that process before starting Traefik, or manage Traefik outside this deployment." >&2
    exit 1
  fi
}

ensure_traefik_ready() {
  local existing_container_id
  local status

  status="$(compose_service_status traefik traefik)"
  case "${status}" in
    running|healthy)
      echo "Traefik is already ${status}; reusing existing container."
      ;;
    missing|exited|dead|created|restarting|removing|paused|unhealthy)
      existing_container_id="$(find_running_traefik_container)"
      if [[ -n "${existing_container_id}" ]]; then
        echo "Found existing Traefik container ${existing_container_id}; reusing it."
        ensure_container_on_traefik_network "${existing_container_id}"
        return 0
      fi
      ensure_ports_available_for_traefik
      echo "Starting Traefik (status: ${status})"
      start_traefik
      ;;
    *)
      existing_container_id="$(find_running_traefik_container)"
      if [[ -n "${existing_container_id}" ]]; then
        echo "Found existing Traefik container ${existing_container_id}; reusing it."
        ensure_container_on_traefik_network "${existing_container_id}"
        return 0
      fi
      ensure_ports_available_for_traefik
      echo "Starting Traefik (status: ${status})"
      start_traefik
      ;;
  esac
}

ensure_postgres_ready() {
  local status

  status="$(compose_service_status prod postgres)"
  case "${status}" in
    healthy)
      echo "Postgres is already healthy; reusing existing container."
      ;;
    running)
      echo "Postgres is running; waiting for health check."
      wait_for_healthy postgres 120
      ;;
    missing|exited|dead|created|restarting|removing|paused|unhealthy)
      echo "Starting Postgres (status: ${status})"
      prod_compose up -d postgres
      wait_for_healthy postgres 120
      ;;
    *)
      echo "Starting Postgres (status: ${status})"
      prod_compose up -d postgres
      wait_for_healthy postgres 120
      ;;
  esac
}

ensure_redis_ready() {
  local status

  status="$(compose_service_status prod redis)"
  case "${status}" in
    healthy)
      echo "Redis is already healthy; reusing existing container."
      ;;
    running)
      echo "Redis is running; waiting for health check."
      wait_for_healthy redis 120
      ;;
    missing|exited|dead|created|restarting|removing|paused|unhealthy)
      echo "Starting Redis (status: ${status})"
      prod_compose up -d redis
      wait_for_healthy redis 120
      ;;
    *)
      echo "Starting Redis (status: ${status})"
      prod_compose up -d redis
      wait_for_healthy redis 120
      ;;
  esac
}

require_command docker

ensure_env_file
load_prod_env_exports
require_release_image
normalize_archive_dir
ensure_archive_source_ready
print_release_summary

ensure_traefik_network
ensure_api_uploads_volume
fetch_release_archive_through_vpn
login_registry_if_configured

echo "Preparing release image"
pull_prod_release_images

echo "Checking shared infrastructure"
ensure_traefik_ready
ensure_postgres_ready
ensure_redis_ready

echo "Running database migrations"
run_prod_migrations

echo "Starting application services"
app_services=(api homepage carplace dashboard admin)

if [[ "${include_pgmanage}" -eq 1 ]]; then
  prod_compose --profile private-tools up -d "${app_services[@]}" pgmanage
else
  prod_compose up -d "${app_services[@]}"
fi

wait_for_healthy api 180
wait_for_healthy homepage 180
wait_for_healthy carplace 180
wait_for_healthy dashboard 180
wait_for_healthy admin 180

if [[ "${skip_prune}" -eq 0 ]]; then
  echo "Pruning dangling images"
  docker image prune -f >/dev/null
fi

echo "Final container status"
prod_compose ps

echo "Production release ${APP_IMAGE:-rentaldrivego/carmanagement}:${IMAGE_TAG} is up and healthy."
