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

NAMESPACE="gitlab"
DEPLOYMENT_NAME="gitlab"
JOB_NAME="gitlab-migrate-to-longhorn"

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
MANIFEST_DIR="${SCRIPT_DIR}/manifest"
PV_PVC_MANIFEST="${MANIFEST_DIR}/pv-pvc.yaml"
MIGRATION_JOB_MANIFEST="${MANIFEST_DIR}/migrate-to-longhorn.yaml"

SOURCE_DATA_PVC="gitlab-data-pvc"
SOURCE_CONFIG_PVC="gitlab-config-pvc"
TARGET_DATA_PVC="gitlab-data-longhorn-pvc"
TARGET_CONFIG_PVC="gitlab-config-longhorn-pvc"

BIND_TIMEOUT_SECONDS=900
JOB_TIMEOUT_SECONDS=7200
POD_STOP_TIMEOUT_SECONDS=300

ORIGINAL_REPLICAS="1"
SCALED_DOWN="false"
DRY_RUN="false"
AUTO_CONFIRM="false"

info() {
  echo "[INFO] $*"
}

warn() {
  echo "[WARN] $*" >&2
}

err() {
  echo "[ERROR] $*" >&2
  exit 1
}

usage() {
  cat <<'EOF'
Usage: migrate-to-longhorn.sh [--dry-run] [--yes] [--help]

Options:
  --dry-run   Show planned actions without changing cluster state.
  --yes       Skip interactive confirmation before scaling down GitLab.
  --help      Show this help.
EOF
}

parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --dry-run)
        DRY_RUN="true"
        ;;
      --yes)
        AUTO_CONFIRM="true"
        ;;
      --help|-h)
        usage
        exit 0
        ;;
      *)
        err "Unknown argument: $1"
        ;;
    esac
    shift
  done
}

restore_deployment_on_error() {
  if [[ "${SCALED_DOWN}" == "true" ]]; then
    warn "Migration failed. Restoring deployment replicas to ${ORIGINAL_REPLICAS}."
    kubectl -n "${NAMESPACE}" scale deployment "${DEPLOYMENT_NAME}" --replicas="${ORIGINAL_REPLICAS}" >/dev/null || true
  fi
}

on_exit() {
  local exit_code=$?
  if [[ ${exit_code} -ne 0 ]]; then
    restore_deployment_on_error
    warn "Script failed with exit code ${exit_code}."
  fi
}
trap on_exit EXIT

require_tool() {
  local tool="$1"
  command -v "${tool}" >/dev/null 2>&1 || err "Required tool not found: ${tool}"
}

require_file() {
  local path="$1"
  [[ -f "${path}" ]] || err "Required file not found: ${path}"
}

wait_for_pvc_bound() {
  local pvc_name="$1"
  local timeout="$2"
  local elapsed=0
  local interval=5

  info "Waiting for PVC ${pvc_name} to become Bound..."
  while true; do
    local phase
    phase="$(kubectl -n "${NAMESPACE}" get pvc "${pvc_name}" -o jsonpath='{.status.phase}' 2>/dev/null || true)"

    if [[ "${phase}" == "Bound" ]]; then
      info "PVC ${pvc_name} is Bound."
      return 0
    fi

    if (( elapsed >= timeout )); then
      err "Timeout while waiting for PVC ${pvc_name} to bind."
    fi

    sleep "${interval}"
    elapsed=$((elapsed + interval))
  done
}

wait_for_gitlab_pods_stopped() {
  local timeout="$1"
  local elapsed=0
  local interval=5

  info "Waiting for pods with label app=gitlab to stop..."
  while true; do
    local count
    count="$(kubectl -n "${NAMESPACE}" get pods -l app=gitlab --no-headers 2>/dev/null | wc -l | tr -d ' ')"

    if [[ "${count}" == "0" ]]; then
      info "All GitLab pods are stopped."
      return 0
    fi

    if (( elapsed >= timeout )); then
      err "Timeout while waiting for GitLab pods to stop."
    fi

    sleep "${interval}"
    elapsed=$((elapsed + interval))
  done
}

wait_for_job_complete() {
  local timeout="$1"

  info "Waiting for migration job ${JOB_NAME} to complete..."
  if kubectl -n "${NAMESPACE}" wait --for=condition=complete "job/${JOB_NAME}" --timeout="${timeout}s" >/dev/null; then
    info "Migration job completed successfully."
    return 0
  fi

  warn "Migration job did not complete in time or failed."
  kubectl -n "${NAMESPACE}" describe job "${JOB_NAME}" || true
  kubectl -n "${NAMESPACE}" logs "job/${JOB_NAME}" --tail=-1 || true
  err "Migration job failed."
}

validate_prerequisites() {
  require_tool kubectl
  require_file "${PV_PVC_MANIFEST}"
  require_file "${MIGRATION_JOB_MANIFEST}"

  kubectl get namespace "${NAMESPACE}" >/dev/null 2>&1 || err "Namespace does not exist: ${NAMESPACE}"
  kubectl -n "${NAMESPACE}" get deployment "${DEPLOYMENT_NAME}" >/dev/null 2>&1 || err "Deployment not found: ${DEPLOYMENT_NAME}"

  kubectl -n "${NAMESPACE}" get pvc "${SOURCE_DATA_PVC}" >/dev/null 2>&1 || err "Source PVC not found: ${SOURCE_DATA_PVC}"
  kubectl -n "${NAMESPACE}" get pvc "${SOURCE_CONFIG_PVC}" >/dev/null 2>&1 || err "Source PVC not found: ${SOURCE_CONFIG_PVC}"
}

confirm_scale_down() {
  if [[ "${AUTO_CONFIRM}" == "true" ]]; then
    return 0
  fi

  echo
  warn "GitLab deployment ${DEPLOYMENT_NAME} in namespace ${NAMESPACE} will be scaled to 0 during migration."
  read -r -p "Type MIGRATE to continue: " confirmation
  if [[ "${confirmation}" != "MIGRATE" ]]; then
    err "Confirmation failed. Aborted by user."
  fi
}

print_dry_run_plan() {
  info "Dry-run mode active. No cluster changes will be made."
  info "Planned steps:"
  info "1) kubectl apply -f ${PV_PVC_MANIFEST}"
  info "2) Wait for PVCs ${TARGET_DATA_PVC} and ${TARGET_CONFIG_PVC} to be Bound"
  info "3) Scale deployment ${DEPLOYMENT_NAME} to 0"
  info "4) Recreate and run job ${JOB_NAME} from ${MIGRATION_JOB_MANIFEST}"
  info "5) Wait for job completion and verify target PVCs"
  info "6) Scale deployment ${DEPLOYMENT_NAME} back to ${ORIGINAL_REPLICAS}"
  info "No PVC switch in deployment is performed by this script."
}

run_migration() {
  ORIGINAL_REPLICAS="$(kubectl -n "${NAMESPACE}" get deployment "${DEPLOYMENT_NAME}" -o jsonpath='{.spec.replicas}')"
  ORIGINAL_REPLICAS="${ORIGINAL_REPLICAS:-1}"

  if [[ "${DRY_RUN}" == "true" ]]; then
    print_dry_run_plan
    return 0
  fi

  confirm_scale_down

  info "Applying PVC manifest to ensure Longhorn target PVCs exist."
  kubectl apply -f "${PV_PVC_MANIFEST}" >/dev/null

  wait_for_pvc_bound "${TARGET_DATA_PVC}" "${BIND_TIMEOUT_SECONDS}"
  wait_for_pvc_bound "${TARGET_CONFIG_PVC}" "${BIND_TIMEOUT_SECONDS}"

  info "Scaling deployment ${DEPLOYMENT_NAME} down to 0 for consistent copy."
  kubectl -n "${NAMESPACE}" scale deployment "${DEPLOYMENT_NAME}" --replicas=0 >/dev/null
  SCALED_DOWN="true"
  wait_for_gitlab_pods_stopped "${POD_STOP_TIMEOUT_SECONDS}"

  info "Recreating migration job ${JOB_NAME}."
  kubectl -n "${NAMESPACE}" delete job "${JOB_NAME}" --ignore-not-found >/dev/null
  kubectl apply -f "${MIGRATION_JOB_MANIFEST}" >/dev/null

  info "Streaming migration job logs."
  kubectl -n "${NAMESPACE}" logs -f "job/${JOB_NAME}" || true

  wait_for_job_complete "${JOB_TIMEOUT_SECONDS}"

  info "Quick verification: target PVCs are still Bound."
  wait_for_pvc_bound "${TARGET_DATA_PVC}" 30
  wait_for_pvc_bound "${TARGET_CONFIG_PVC}" 30

  info "Scaling deployment ${DEPLOYMENT_NAME} back to ${ORIGINAL_REPLICAS}."
  kubectl -n "${NAMESPACE}" scale deployment "${DEPLOYMENT_NAME}" --replicas="${ORIGINAL_REPLICAS}" >/dev/null
  SCALED_DOWN="false"

  info "Migration finished successfully."
  info "No deployment PVC switch was done by this script."
  info "NFS PVC/PV (gitlab-git-pvc / gitlab-git-pv) were not changed."
  info "If you want to switch GitLab to Longhorn later, apply the updated deployment manifest manually."
}

main() {
  parse_args "$@"
  info "Starting GitLab local PVC to Longhorn migration."
  if [[ "${DRY_RUN}" == "true" ]]; then
    info "Running in dry-run mode."
  fi
  validate_prerequisites
  run_migration
}

main "$@"
