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

echo "Script version: 2.0.2"

K_MANAGED_BY="smp-cron-discovery"

required_env() {
  local name="$1"
  local value="${!name:-}"
  if [[ -z "$value" ]]; then
    echo "Missing required env: $name" >&2
    exit 1
  fi
  printf '%s' "$value"
}

optional_env() {
  local name="$1"
  local fallback="${2:-}"
  local value="${!name:-}"
  if [[ -z "$value" ]]; then
    printf '%s' "$fallback"
    return
  fi
  printf '%s' "$value"
}

as_bool() {
  local value="${1:-}"
  [[ "${value,,}" == "true" ]]
}

normalize_base_url() {
  local url="$1"
  echo "${url%/}"
}

join_url() {
  local base_url="$1"
  local path="$2"
  if [[ "$path" != /* ]]; then
    path="/$path"
  fi
  printf '%s%s' "$(normalize_base_url "$base_url")" "$path"
}

append_query_param() {
  local url="$1"
  local key="$2"
  local value="$3"
  local separator='?'
  [[ "$url" == *\?* ]] && separator='&'
  printf '%s%s%s=%s' "$url" "$separator" "$key" "$value"
}

sanitize_job_name() {
  local input="$1"
  local fallback_path="$2"
  local normalized
  normalized="$(echo "${input,,}" | sed -E 's/[^a-z0-9-]+/-/g; s/-+/-/g; s/^-+//; s/-+$//')"
  if [[ -n "$normalized" ]]; then
    echo "${normalized:0:500}"
    return
  fi
  local from_path
  from_path="$(echo "${fallback_path#/}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9/]+//g; s#/#-#g')"
  if [[ -z "$from_path" ]]; then
    from_path="cron-job"
  fi
  echo "${from_path:0:500}"
}

append_env_suffix() {
  local job_name="$1"
  local env_name="$2"
  local with_suffix="${job_name}-${env_name,,}"
  echo "${with_suffix:0:500}"
}

build_basic_auth_header() {
  local username password b64
  username="$(required_env BASIC_AUTH_USER)"
  password="$(required_env BASIC_AUTH_PASSWORD)"
  b64="$(printf '%s:%s' "$username" "$password" | base64 | tr -d '\n')"
  printf 'Authorization: Basic %s' "$b64"
}

build_token_auth_header() {
  local token
  token="$(required_env TOKEN_AUTH_TOKEN)"
  printf 'Authorization: Bearer %s' "$token"
}

to_gcloud_header() {
  local header="$1"
  printf '%s' "$header" | sed -E 's/:[[:space:]]*/=/'
}

has_content_type_header() {
  local headers_csv="${1:-}"
  local headers_lower
  headers_lower="$(printf '%s' "$headers_csv" | tr '[:upper:]' '[:lower:]')"
  [[ "$headers_lower" == *"content-type="* ]]
}

ensure_json_content_type_header() {
  local headers_csv="${1:-}"
  if has_content_type_header "$headers_csv"; then
    printf '%s' "$headers_csv"
    return
  fi

  if [[ -n "$headers_csv" ]]; then
    printf '%s,Content-Type=application/json' "$headers_csv"
    return
  fi

  printf 'Content-Type=application/json'
}

ensure_jq() {
  if command -v jq >/dev/null 2>&1; then
    return
  fi
  if command -v apt-get >/dev/null 2>&1; then
    apt-get update >/dev/null
    apt-get install -y jq >/dev/null
  elif command -v apk >/dev/null 2>&1; then
    apk add --no-cache jq >/dev/null
  else
    echo "ERROR: no package manager found (tried apt-get, apk)" >&2
    exit 1
  fi
}

main() {
  ensure_jq

  local project region service_name scheduler_sa scheduler_base_url time_zone prune_orphans discovery_path discovery_auth_type managed_description env_name
  local default_attempt_deadline default_max_retry_attempts
  project="$(required_env PROJECT_ID)"
  region="$(required_env REGION)"
  service_name="$(required_env _SERVICE_NAME)"
  scheduler_sa="$(required_env SCHEDULER_SA)"
  scheduler_base_url="$(required_env SCHEDULER_BASE_URL)"
  time_zone="$(optional_env TIME_ZONE "Europe/Rome")"
  prune_orphans="$(optional_env SCHEDULER_PRUNE_ORPHANS "false")"
  discovery_path="$(required_env DISCOVERY_PATH)"
  discovery_auth_type="$(optional_env DISCOVERY_AUTH_TYPE "basic")"
  default_attempt_deadline="$(optional_env SCHEDULER_DEFAULT_ATTEMPT_DEADLINE "3m")"
  default_max_retry_attempts="$(optional_env SCHEDULER_DEFAULT_MAX_RETRY_ATTEMPTS "2")"
  managed_description="managed-by=${K_MANAGED_BY};service=${service_name}"
  env_name="$(required_env ENVIRONMENT)"
  managed_description="${managed_description};env=${env_name,,}"

  local discovery_url curl_auth_header discovery_json_file
  discovery_url="$(join_url "$scheduler_base_url" "$discovery_path")"
  discovery_url="$(append_query_param "$discovery_url" "env" "$env_name")"
  discovery_json_file="$(mktemp)"

  case "$discovery_auth_type" in
    basic)
      curl_auth_header="$(build_basic_auth_header)"
      ;;
    token)
      curl_auth_header="$(build_token_auth_header)"
      ;;
    none)
      curl_auth_header=""
      ;;
    *)
      echo "Unsupported DISCOVERY_AUTH_TYPE: $discovery_auth_type" >&2
      exit 1
      ;;
  esac

  if [[ -n "$curl_auth_header" ]]; then
    curl -fsS -H "$curl_auth_header" "$discovery_url" > "$discovery_json_file"
  else
    curl -fsS "$discovery_url" > "$discovery_json_file"
  fi

  if ! jq -e '.jobs | type == "array" and length > 0' "$discovery_json_file" >/dev/null; then
    echo "No jobs discovered from endpoint; skipping scheduler sync."
    exit 0
  fi

  declare -A desired_jobs=()

  while IFS= read -r job_json; do
    local job_id job_name path method schedule deadline max_attempts auth_type headers_csv auth_header action
    job_id="$(jq -r '.jobId // empty' <<< "$job_json")"
    path="$(jq -r '.path // empty' <<< "$job_json")"
    method="$(jq -r '.method // "POST"' <<< "$job_json")"
    schedule="$(jq -r '.schedule // empty' <<< "$job_json")"
    deadline="$(jq -r '.deadline // empty' <<< "$job_json")"
    max_attempts="$(jq -r '.maxAttempts // empty' <<< "$job_json")"
    auth_type="$(jq -r '.authType // ""' <<< "$job_json")"

    if [[ -z "$path" || -z "$schedule" ]]; then
      echo "Invalid discovered job payload: path and schedule are required." >&2
      exit 1
    fi
    if [[ -z "$deadline" ]]; then
      deadline="$default_attempt_deadline"
    fi
    if [[ -z "$max_attempts" ]]; then
      max_attempts="$default_max_retry_attempts"
    fi
    if ! [[ "$max_attempts" =~ ^[0-9]+$ ]]; then
      echo "Invalid maxAttempts for job ${job_id:-$path}: ${max_attempts}" >&2
      exit 1
    fi

    job_name="$(sanitize_job_name "$job_id" "$path")"
    job_name="$(append_env_suffix "$job_name" "$env_name")"
    desired_jobs["$job_name"]=1

    if gcloud scheduler jobs describe "$job_name" --project="$project" --location="$region" >/dev/null 2>&1; then
      action="update"
    else
      action="create"
    fi

    headers_csv="$(jq -r '(.headers // {}) | to_entries | map("\(.key)=\(.value|tostring)") | join(",")' <<< "$job_json")"
    case "$auth_type" in
      basic)
        auth_header="$(to_gcloud_header "$(build_basic_auth_header)")"
        ;;
      token)
        auth_header="$(to_gcloud_header "$(build_token_auth_header)")"
        ;;
      "")
        auth_header=""
        ;;
      *)
        echo "Unsupported authType for job ${job_name}: ${auth_type}" >&2
        exit 1
        ;;
    esac
    if [[ -n "$auth_header" ]]; then
      if [[ -n "$headers_csv" ]]; then
        headers_csv="${headers_csv},${auth_header}"
      else
        headers_csv="${auth_header}"
      fi
    fi
    if [[ -n "$env_name" ]]; then
      headers_csv="$(ensure_json_content_type_header "$headers_csv")"
    fi

    echo
    echo "🔄 Scheduler job: ${action} ${job_name}"
    args=(
      scheduler jobs "$action" http "$job_name"
      "--project=${project}"
      "--location=${region}"
      --schedule "$schedule"
      --time-zone "$time_zone"
      --description "$managed_description"
      --uri "$(join_url "$scheduler_base_url" "$path")"
      --http-method "$method"
      --attempt-deadline "$deadline"
      --max-retry-attempts "$max_attempts"
    )
    if [[ -z "$auth_type" ]]; then
      args+=(--oidc-service-account-email "$scheduler_sa")
    fi
    if [[ "$action" == "create" ]]; then
      if [[ -n "$headers_csv" ]]; then
        args+=(--headers "$headers_csv")
      fi
      if [[ -n "$env_name" ]]; then
        args+=(--message-body "{\"env\":\"${env_name}\"}")
      fi
      gcloud "${args[@]}"
    else
      update_args=("${args[@]}" --clear-headers)
      if [[ -n "$env_name" ]]; then
        update_args+=(--message-body "{\"env\":\"${env_name}\"}")
      fi
      gcloud "${update_args[@]}"

      if [[ -n "$headers_csv" ]]; then
        gcloud scheduler jobs update http "$job_name" \
          "--project=${project}" \
          "--location=${region}" \
          --update-headers "$headers_csv"
      fi
    fi
    echo "✔ Job ${job_name} (${action}) completato."
  done < <(jq -c '.jobs[]' "$discovery_json_file")

  if as_bool "$prune_orphans"; then
    while IFS= read -r existing_job; do
      local full_name name description
      full_name="$(jq -r '.name' <<< "$existing_job")"
      name="${full_name##*/}"
      description="$(jq -r '.description // ""' <<< "$existing_job")"

      if [[ "$description" == *"$managed_description"* ]] && [[ -z "${desired_jobs[$name]+x}" ]]; then
        echo
        echo "🧹 Removing orphan scheduler job: ${name} (${full_name})"
        gcloud scheduler jobs delete "$name" \
          "--project=${project}" \
          "--location=${region}" \
          --quiet
        echo "✔ Orphan job removed: ${name}"
      fi
    done < <(gcloud scheduler jobs list --project="$project" --location="$region" --format=json | jq -c '.[]')
  fi

  rm -f "$discovery_json_file"
}

main "$@"
