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

usage() {
  echo "Usage: sudo ./update.sh /opt/zimphony-webmail/<release>.zip" >&2
  exit 1
}

[[ "$#" == "1" ]] || usage
zip_path="$1"
[[ -f "$zip_path" ]] || { echo "Release zip not found: $zip_path" >&2; exit 1; }

new_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
base_dir="$(cd "$new_root/.." && pwd)"
timestamp="$(date +%Y%m%d-%H%M%S)"

command -v docker >/dev/null 2>&1 || { echo "Docker is required." >&2; exit 1; }
command -v unzip >/dev/null 2>&1 || { echo "unzip is required." >&2; exit 1; }
unzip -tq "$zip_path" >/dev/null || { echo "Release zip is invalid: $zip_path" >&2; exit 1; }

edition_dirs_for_root() {
  local root="$1"
  find "$root/deploy/docker" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null \
    | while read -r candidate; do
        case "$candidate" in
          mail-only|mail-google|mail-native-zimbra|mailmali-google-connect)
            [[ -f "$root/deploy/docker/$candidate/docker-compose.yml" ]] && printf '%s\n' "$candidate"
            ;;
        esac
      done
}

release_root_from_compose_file() {
  local compose_file="$1"
  local compose_dir
  [[ -n "$compose_file" ]] || return 0
  compose_dir="$(cd "$(dirname "$compose_file")" 2>/dev/null && pwd || true)"
  [[ -n "$compose_dir" && -f "$compose_dir/docker-compose.yml" ]] || return 0
  if [[ "$(basename "$compose_dir")" == "$edition" && "$(basename "$(dirname "$compose_dir")")" == "docker" && "$(basename "$(dirname "$(dirname "$compose_dir")")")" == "deploy" ]]; then
    cd "$(dirname "$(dirname "$(dirname "$compose_dir")")")" && pwd
  fi
}

resolve_current_release_root() {
  local explicit_root="$1"
  local container_id="$2"
  local candidate=""
  local config_files=""
  local matches=()

  if [[ -n "$explicit_root" && -d "$explicit_root" ]]; then
    candidate="$(cd "$explicit_root" && pwd)"
    mapfile -t matches < <(edition_dirs_for_root "$candidate")
    if [[ "${#matches[@]}" == "1" ]]; then
      printf '%s\n' "$candidate"
      return
    fi
  fi

  if [[ -n "$container_id" ]]; then
    config_files="$(docker inspect -f '{{ index .Config.Labels "com.docker.compose.project.config_files" }}' "$container_id" 2>/dev/null || true)"
    IFS=',' read -r -a matches <<< "$config_files"
    for candidate in "${matches[@]}"; do
      candidate="$(release_root_from_compose_file "$candidate")"
      [[ -n "$candidate" ]] || continue
      [[ "$candidate" != "$new_root" ]] || continue
      printf '%s\n' "$candidate"
      return
    done
  fi

  matches=()
  while IFS= read -r candidate; do
    [[ "$candidate" != "$new_root" ]] || continue
    matches+=("$candidate")
  done < <(
    find "$base_dir" -mindepth 1 -maxdepth 1 -type d -name 'zimphony-webmail-*' -printf '%p\n' 2>/dev/null \
      | while read -r candidate; do
          [[ -f "$candidate/deploy/docker/$edition/docker-compose.yml" ]] && printf '%s\n' "$(cd "$candidate" && pwd)"
        done
  )
  if [[ "${#matches[@]}" == "1" ]]; then
    printf '%s\n' "${matches[0]}"
  fi
}

resolve_backend_data_volume() {
  local default_volume="$1"
  local mounted_volume=""
  local container_id=""
  local candidate=""
  local volumes=()
  local unique=()
  declare -A seen=()

  if [[ -n "${BACKEND_DATA_VOLUME:-}" ]]; then
    printf '%s\n' "$BACKEND_DATA_VOLUME"
    return
  fi

  if docker volume inspect "$default_volume" >/dev/null 2>&1; then
    printf '%s\n' "$default_volume"
    return
  fi

  while IFS= read -r container_id; do
    [[ -n "$container_id" ]] || continue
    mounted_volume="$(docker inspect -f '{{ range .Mounts }}{{ if eq .Destination "/app/data" }}{{ .Name }}{{ end }}{{ end }}' "$container_id" 2>/dev/null || true)"
    [[ -n "$mounted_volume" ]] && volumes+=("$mounted_volume")
  done < <(docker ps -aq --filter 'label=com.docker.compose.service=backend')

  if [[ "${#volumes[@]}" == "0" ]]; then
    while IFS= read -r candidate; do
      [[ -n "$candidate" ]] && volumes+=("$candidate")
    done < <(docker volume ls -q | while read -r candidate; do
      [[ "$candidate" == *_backend-data ]] && printf '%s\n' "$candidate"
    done)
  fi

  for candidate in "${volumes[@]}"; do
    if [[ -z "${seen[$candidate]+x}" ]]; then
      unique+=("$candidate")
      seen[$candidate]=1
    fi
  done

  if [[ "${#unique[@]}" == "1" ]]; then
    printf '%s\n' "${unique[0]}"
    return
  fi

  echo "Existing volume not found: $default_volume" >&2
  if [[ "${#unique[@]}" -gt "1" ]]; then
    echo "Detected multiple backend-data volume candidates:" >&2
    for candidate in "${unique[@]}"; do
      echo "  $candidate" >&2
    done
  fi
  echo "Set BACKEND_DATA_VOLUME to the current backend-data volume and retry." >&2
  return 1
}

mapfile -t edition_dirs < <(edition_dirs_for_root "$new_root")
if [[ "${#edition_dirs[@]}" != "1" ]]; then
  echo "Cannot identify exactly one Edition in the new release." >&2
  exit 1
fi
edition="${edition_dirs[0]}"
new_compose_dir="$new_root/deploy/docker/$edition"
new_compose_file="$new_compose_dir/docker-compose.yml"

volume_name="$(resolve_backend_data_volume "${edition}_backend-data")"
docker volume inspect "$volume_name" >/dev/null 2>&1 || { echo "Existing volume not found: $volume_name" >&2; exit 1; }

compose_project="${COMPOSE_PROJECT_NAME:-}"
if [[ -z "$compose_project" ]]; then
  compose_project="$(docker volume inspect -f '{{ index .Labels "com.docker.compose.project" }}' "$volume_name" 2>/dev/null || true)"
fi
if [[ -z "$compose_project" ]]; then
  suffix="_backend-data"
  [[ "$volume_name" == *"$suffix" ]] || { echo "Cannot identify Compose project." >&2; exit 1; }
  compose_project="${volume_name%$suffix}"
fi

backend_container="$(docker ps -aq \
  --filter "label=com.docker.compose.project=$compose_project" \
  --filter 'label=com.docker.compose.service=backend' | head -n 1)"
current_root="${CURRENT_RELEASE:-}"
if [[ -z "$current_root" && -n "$backend_container" ]]; then
  current_root="$(docker inspect -f '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}' "$backend_container" 2>/dev/null || true)"
fi
current_root="$(resolve_current_release_root "$current_root" "$backend_container")"
if [[ -z "$current_root" || ! -d "$current_root" ]]; then
  echo "Cannot identify the current release directory." >&2
  echo "Set CURRENT_RELEASE to the old release directory and retry." >&2
  exit 1
fi
if [[ "$(cd "$current_root" && pwd)" == "$new_root" ]]; then
  echo "The new release directory is already active; no update was performed." >&2
  exit 1
fi

mapfile -t current_edition_dirs < <(edition_dirs_for_root "$current_root")
if [[ "${#current_edition_dirs[@]}" != "1" || "${current_edition_dirs[0]}" != "$edition" ]]; then
  echo "Edition mismatch: current=${current_edition_dirs[*]:-unknown}, new=$edition" >&2
  echo "No containers were stopped." >&2
  exit 1
fi

current_compose_dir="$current_root/deploy/docker/$edition"
current_compose_file="$current_compose_dir/docker-compose.yml"
backup_dir="$base_dir/backups/update-${edition}-${timestamp}"
mkdir -p "$backup_dir"
cp -p "$current_compose_dir/backend.env" "$backup_dir/backend.env"
cp -p "$current_compose_dir/frontend.env" "$backup_dir/frontend.env"

if [[ -n "$backend_container" ]]; then
  docker exec "$backend_container" sh -lc '
    set -eu
    mkdir -p /app/data
    for name in google_tokens.json google_task_colors.json google_calendar_watches.json; do
      target="/app/data/$name"
      if [ -s "$target" ]; then
        continue
      fi
      for source in "/app/backend.dist/data/$name" "/app/backend/data/$name"; do
        if [ -s "$source" ]; then
          cp -p "$source" "$target"
          chmod 600 "$target" 2>/dev/null || true
          break
        fi
      done
    done
  ' >/dev/null 2>&1 || true
fi

echo "Backing up $volume_name..."
docker run --rm -v "$volume_name:/data:ro" -v "$backup_dir:/backup" alpine:3.20 \
  tar czf /backup/backend-data.tar.gz -C /data .

cp -p "$current_compose_dir/backend.env" "$new_compose_dir/backend.env"
cp -p "$current_compose_dir/frontend.env" "$new_compose_dir/frontend.env"

docker compose -p "$compose_project" -f "$new_compose_file" config >/dev/null
echo "Stopping current release..."
docker compose -p "$compose_project" -f "$current_compose_file" down --remove-orphans
echo "Starting new release from: $new_root"
docker compose -p "$compose_project" -f "$new_compose_file" up -d --build

echo
echo "Update completed. Run the verification command:"
echo "  sudo $new_root/verify-update.sh"
echo "Backup: $backup_dir"
