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

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
OUTPUT_DIR="$ROOT_DIR/config/.generated"
OUTPUT_FILE="$OUTPUT_DIR/auth-config.env"
MODE="${1:-build}"

if [[ -f "$ROOT_DIR/.env" ]]; then
  # shellcheck disable=SC1091
  set -a
  source "$ROOT_DIR/.env"
  set +a
fi

USERFRONT_URL="${USERFRONT_URL:-http://localhost:5000}"
OATHKEEPER_PUBLIC_URL="${OATHKEEPER_PUBLIC_URL:-$USERFRONT_URL}"
HYDRA_PUBLIC_URL="${HYDRA_PUBLIC_URL:-${OATHKEEPER_PUBLIC_URL%/}/oidc}"
HYDRA_ADMIN_URL="${HYDRA_ADMIN_URL:-http://hydra:4445}"
KRATOS_UI_URL="${KRATOS_UI_URL:-http://localhost:5000}"
ADMINFRONT_URL="${ADMINFRONT_URL:-https://sadmin.hmac.kr}"
DEVFRONT_URL="${DEVFRONT_URL:-https://sdev.hmac.kr}"
ORGFRONT_URL="${ORGFRONT_URL:-https://sorg.hmac.kr}"
ADMINFRONT_CALLBACK_URLS="${ADMINFRONT_CALLBACK_URLS:-${ADMINFRONT_URL%/}/auth/callback}"
DEVFRONT_CALLBACK_URLS="${DEVFRONT_CALLBACK_URLS:-${DEVFRONT_URL%/}/auth/callback}"
ORGFRONT_CALLBACK_URLS="${ORGFRONT_CALLBACK_URLS:-${ORGFRONT_URL%/}/auth/callback}"
KRATOS_ALLOWED_RETURN_URLS_EXTRA="${KRATOS_ALLOWED_RETURN_URLS_EXTRA:-}"

declare -a WARNINGS=()

fail() {
  echo "[auth-config] ERROR: $1" >&2
  exit 1
}

warn() {
  WARNINGS+=("$1")
}

validate_dotenv_line_safety() {
  local key="$1"
  local env_file="$ROOT_DIR/.env"
  [[ -f "$env_file" ]] || return 0

  local raw_line
  raw_line="$(grep -E "^${key}=" "$env_file" | tail -n 1 || true)"
  [[ -n "$raw_line" ]] || return 0

  if [[ "$raw_line" == *" #"* ]]; then
    fail ".env line for $key contains inline comment. Use comment-only line above the key."
  fi

  if [[ "$raw_line" =~ [[:space:]]+$ ]]; then
    fail ".env line for $key has trailing whitespace."
  fi
}

trim() {
  local value="$1"
  value="${value#"${value%%[![:space:]]*}"}"
  value="${value%"${value##*[![:space:]]}"}"
  printf '%s' "$value"
}

csv_to_lines() {
  local csv="$1"
  printf '%s\n' "$csv" | tr ',' '\n' | while IFS= read -r raw; do
    local item
    item="$(trim "$raw")"
    if [[ -n "$item" ]]; then
      printf '%s\n' "$item"
    fi
  done
}

list_to_lines() {
  local raw="$1"
  raw="$(trim "$raw")"
  if [[ -z "$raw" || "$raw" == "[]" ]]; then
    return 0
  fi

  if [[ "$raw" =~ ^\[(.*)\]$ ]]; then
    local inner="${BASH_REMATCH[1]}"
    inner="$(trim "$inner")"
    if [[ -z "$inner" ]]; then
      return 0
    fi

    printf '%s\n' "$inner" | tr ',' '\n' | while IFS= read -r token; do
      local item
      item="$(trim "$token")"
      item="${item#\"}"
      item="${item%\"}"
      item="${item#\'}"
      item="${item%\'}"
      item="$(trim "$item")"
      if [[ -n "$item" ]]; then
        printf '%s\n' "$item"
      fi
    done
    return 0
  fi

  csv_to_lines "$raw"
}

is_http_url() {
  local url="$1"
  [[ "$url" =~ ^https?://[^[:space:]]+$ ]]
}

canonicalize_url() {
  local url="$1"
  if [[ "$url" =~ ^(https?://[^/]+)(/.*)?$ ]]; then
    local origin="${BASH_REMATCH[1]}"
    local path="${BASH_REMATCH[2]:-}"
    if [[ -z "$path" || "$path" == "/" ]]; then
      printf '%s' "$origin"
      return
    fi
    path="${path%/}"
    if [[ -z "$path" ]]; then
      path="/"
    fi
    printf '%s%s' "$origin" "$path"
    return
  fi
  printf '%s' "$url"
}

is_origin_like_url() {
  local url="$1"
  [[ "$url" =~ ^https?://[^/]+/?$ ]]
}

url_path() {
  local url="$1"
  if [[ "$url" =~ ^https?://[^/]+(/.*)?$ ]]; then
    local path="${BASH_REMATCH[1]:-/}"
    printf '%s' "$path"
    return
  fi
  printf ''
}

json_escape() {
  local value="$1"
  value="${value//\\/\\\\}"
  value="${value//\"/\\\"}"
  printf '%s' "$value"
}

join_csv() {
  local -n arr_ref=$1
  local out=""
  local first=1
  for item in "${arr_ref[@]}"; do
    if [[ $first -eq 1 ]]; then
      out="$item"
      first=0
    else
      out="$out,$item"
    fi
  done
  printf '%s' "$out"
}

to_json_array() {
  local -n arr_ref=$1
  local json="["
  local first=1
  for item in "${arr_ref[@]}"; do
    local escaped
    escaped="$(json_escape "$item")"
    if [[ $first -eq 1 ]]; then
      json="$json\"$escaped\""
      first=0
    else
      json="$json,\"$escaped\""
    fi
  done
  json="$json]"
  printf '%s' "$json"
}

collect_values() {
  declare -ga ADMIN_CALLBACKS=()
  declare -ga DEV_CALLBACKS=()
  declare -ga ORG_CALLBACKS=()
  declare -ga EXTRA_ALLOWED_RETURNS=()

  while IFS= read -r item; do
    ADMIN_CALLBACKS+=("$item")
  done < <(csv_to_lines "$ADMINFRONT_CALLBACK_URLS")

  while IFS= read -r item; do
    DEV_CALLBACKS+=("$item")
  done < <(csv_to_lines "$DEVFRONT_CALLBACK_URLS")

  while IFS= read -r item; do
    ORG_CALLBACKS+=("$item")
  done < <(csv_to_lines "$ORGFRONT_CALLBACK_URLS")

  while IFS= read -r item; do
    EXTRA_ALLOWED_RETURNS+=("$item")
  done < <(list_to_lines "$KRATOS_ALLOWED_RETURN_URLS_EXTRA")
}

validate_urls() {
  local key="$1"
  local value="$2"
  if ! is_http_url "$value"; then
    fail "$key must be a valid http/https URL: $value"
  fi
}

validate_callback_group() {
  local group_name="$1"
  local expected_path="$2"
  shift 2
  local urls=("$@")

  if [[ ${#urls[@]} -eq 0 ]]; then
    fail "$group_name is empty"
  fi

  local matched_expected=0
  local has_path=0
  for url in "${urls[@]}"; do
    validate_urls "$group_name entry" "$url"
    local canonical
    canonical="$(canonicalize_url "$url")"
    if [[ "$url" != "$canonical" ]]; then
      fail "$group_name entry must not end with trailing slash: $url"
    fi
    local path
    path="$(url_path "$canonical")"
    if [[ -n "$path" && "$path" != "/" ]]; then
      has_path=1
    fi
    if [[ "$path" == "$expected_path" ]]; then
      matched_expected=1
    fi
  done

  if [[ $has_path -eq 0 ]]; then
    fail "$group_name must include path segment (callback path required)"
  fi

  if [[ $matched_expected -eq 0 ]]; then
    warn "$group_name does not include recommended path: $expected_path"
  fi
}

validate_gateway_mapping() {
  validate_urls "HYDRA_PUBLIC_URL" "$HYDRA_PUBLIC_URL"
  validate_urls "USERFRONT_URL" "$USERFRONT_URL"
  validate_urls "KRATOS_UI_URL" "$KRATOS_UI_URL"

  local mode=""
  if [[ "$HYDRA_PUBLIC_URL" =~ ^https?://hydra(:[0-9]+)?(/|$) ]]; then
    mode="direct_match"
  else
    mode="mapped_match"
    if ! grep -Eq 'location /oidc' "$ROOT_DIR/gateway/nginx.conf"; then
      mode="unmapped_fail"
    fi
    if ! grep -Eq '"url": "<\.\*>://<(\.\*|\[\^/\]\+)>/oidc/oauth2/<\.\*>"' "$ROOT_DIR/docker/ory/oathkeeper/rules.json"; then
      mode="unmapped_fail"
    fi
    if ! grep -Eq '"strip_path(_prefix)?": "/oidc"' "$ROOT_DIR/docker/ory/oathkeeper/rules.json"; then
      mode="unmapped_fail"
    fi
  fi

  if [[ "$mode" == "unmapped_fail" ]]; then
    fail "Public/Internal Hydra URL mismatch exists but gateway mapping rules are incomplete"
  fi

  OIDC_HYDRA_URL_MATCH_MODE="$mode"
}

build_allowed_return_urls() {
  declare -ga KRATOS_ALLOWED_RETURN_URLS=()
  declare -gA _seen_allowed=()

  add_allowed_url() {
    local candidate="$1"
    candidate="$(trim "$candidate")"
    [[ -z "$candidate" ]] && return
    validate_urls "allowed_return_url" "$candidate"
    candidate="$(canonicalize_url "$candidate")"
    if [[ -z "${_seen_allowed[$candidate]:-}" ]]; then
      KRATOS_ALLOWED_RETURN_URLS+=("$candidate")
      _seen_allowed["$candidate"]=1
    fi
  }

  add_allowed_with_slash_variant() {
    local candidate="$1"
    add_allowed_url "$candidate"
    local normalized
    normalized="$(canonicalize_url "$candidate")"
    if is_origin_like_url "$candidate"; then
      add_allowed_url "${normalized}/"
    fi
  }

  add_userfront_return_urls() {
    local base="$1"
    local normalized
    normalized="$(canonicalize_url "$base")"
    [[ -n "$normalized" ]] || return

    add_allowed_with_slash_variant "$normalized"
    add_allowed_url "${normalized}/ko"
    add_allowed_url "${normalized}/ko/"
    add_allowed_url "${normalized}/en"
    add_allowed_url "${normalized}/en/"
    add_allowed_url "${normalized}/auth/callback"
    add_allowed_url "${normalized}/ko/auth/callback"
    add_allowed_url "${normalized}/en/auth/callback"
  }

  add_allowed_with_slash_variant "$KRATOS_UI_URL"
  add_userfront_return_urls "$USERFRONT_URL"

  for url in "${ADMIN_CALLBACKS[@]}"; do
    add_allowed_url "$url"
  done
  for url in "${DEV_CALLBACKS[@]}"; do
    add_allowed_url "$url"
  done
  for url in "${ORG_CALLBACKS[@]}"; do
    add_allowed_url "$url"
  done
  for url in "${EXTRA_ALLOWED_RETURNS[@]}"; do
    add_allowed_url "$url"
  done

  if [[ ${#KRATOS_ALLOWED_RETURN_URLS[@]} -eq 0 ]]; then
    fail "KRATOS allowed return URL list is empty"
  fi
}

write_output() {
  mkdir -p "$OUTPUT_DIR"
  local admin_csv dev_csv org_csv returns_json
  admin_csv="$(join_csv ADMIN_CALLBACKS)"
  dev_csv="$(join_csv DEV_CALLBACKS)"
  org_csv="$(join_csv ORG_CALLBACKS)"
  returns_json="$(to_json_array KRATOS_ALLOWED_RETURN_URLS)"

  cat >"$OUTPUT_FILE" <<EOF
# Generated by scripts/auth_config.sh
# Do not edit manually.
ADMINFRONT_CALLBACK_URLS=$admin_csv
DEVFRONT_CALLBACK_URLS=$dev_csv
ORGFRONT_CALLBACK_URLS=$org_csv
KRATOS_ALLOWED_RETURN_URLS_JSON=$returns_json
OIDC_HYDRA_URL_MATCH_MODE=$OIDC_HYDRA_URL_MATCH_MODE
EOF
}

validate_compose_wiring() {
  grep -Eq 'KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=\$\{KRATOS_ALLOWED_RETURN_URLS_JSON' "$ROOT_DIR/compose.ory.yaml" \
    || fail "compose.ory.yaml is not wired to KRATOS_ALLOWED_RETURN_URLS_JSON"
  grep -Eq 'ADMINFRONT_CALLBACK_URLS' "$ROOT_DIR/compose.ory.yaml" \
    || fail "compose.ory.yaml is not wired to ADMINFRONT_CALLBACK_URLS"
  grep -Eq 'DEVFRONT_CALLBACK_URLS' "$ROOT_DIR/compose.ory.yaml" \
    || fail "compose.ory.yaml is not wired to DEVFRONT_CALLBACK_URLS"
  grep -Eq 'ORGFRONT_CALLBACK_URLS' "$ROOT_DIR/compose.ory.yaml" \
    || fail "compose.ory.yaml is not wired to ORGFRONT_CALLBACK_URLS"
}

verify_runtime_hydra_clients() {
  if ! command -v docker >/dev/null 2>&1; then
    warn "docker command is unavailable; runtime hydra verification skipped"
    return
  fi

  if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^ory_hydra$'; then
    warn "ory_hydra is not running; runtime hydra verification skipped"
    return
  fi

  local admin_info dev_info org_info
  if ! admin_info="$(docker exec ory_hydra hydra get oauth2-client --endpoint "$HYDRA_ADMIN_URL" adminfront 2>/dev/null)"; then
    fail "failed to read hydra client 'adminfront' from running container"
  fi
  if ! dev_info="$(docker exec ory_hydra hydra get oauth2-client --endpoint "$HYDRA_ADMIN_URL" devfront 2>/dev/null)"; then
    fail "failed to read hydra client 'devfront' from running container"
  fi
  if ! org_info="$(docker exec ory_hydra hydra get oauth2-client --endpoint "$HYDRA_ADMIN_URL" orgfront 2>/dev/null)"; then
    fail "failed to read hydra client 'orgfront' from running container"
  fi

  for callback in "${ADMIN_CALLBACKS[@]}"; do
    if ! grep -Fq "$callback" <<<"$admin_info"; then
      fail "adminfront hydra client does not include callback: $callback"
    fi
  done
  for callback in "${DEV_CALLBACKS[@]}"; do
    if ! grep -Fq "$callback" <<<"$dev_info"; then
      fail "devfront hydra client does not include callback: $callback"
    fi
  done
  for callback in "${ORG_CALLBACKS[@]}"; do
    if ! grep -Fq "$callback" <<<"$org_info"; then
      fail "orgfront hydra client does not include callback: $callback"
    fi
  done
}

run_validation() {
  validate_dotenv_line_safety "USERFRONT_URL"
  validate_dotenv_line_safety "BACKEND_URL"
  validate_dotenv_line_safety "OATHKEEPER_PUBLIC_URL"
  validate_dotenv_line_safety "HYDRA_PUBLIC_URL"
  validate_dotenv_line_safety "HYDRA_ADMIN_URL"
  validate_dotenv_line_safety "KRATOS_BROWSER_URL"
  validate_dotenv_line_safety "KRATOS_UI_URL"
  validate_dotenv_line_safety "ADMINFRONT_URL"
  validate_dotenv_line_safety "DEVFRONT_URL"
  validate_dotenv_line_safety "ORGFRONT_URL"
  validate_dotenv_line_safety "ADMINFRONT_CALLBACK_URLS"
  validate_dotenv_line_safety "DEVFRONT_CALLBACK_URLS"
  validate_dotenv_line_safety "ORGFRONT_CALLBACK_URLS"

  if [[ -n "$ADMINFRONT_URL" ]]; then
    validate_urls "ADMINFRONT_URL" "$ADMINFRONT_URL"
  fi
  if [[ -n "$DEVFRONT_URL" ]]; then
    validate_urls "DEVFRONT_URL" "$DEVFRONT_URL"
  fi
  if [[ -n "$ORGFRONT_URL" ]]; then
    validate_urls "ORGFRONT_URL" "$ORGFRONT_URL"
  fi

  collect_values
  validate_callback_group "ADMINFRONT_CALLBACK_URLS" "/auth/callback" "${ADMIN_CALLBACKS[@]}"
  validate_callback_group "DEVFRONT_CALLBACK_URLS" "/auth/callback" "${DEV_CALLBACKS[@]}"
  validate_callback_group "ORGFRONT_CALLBACK_URLS" "/auth/callback" "${ORG_CALLBACKS[@]}"
  validate_gateway_mapping
  build_allowed_return_urls
}

print_summary() {
  echo "[auth-config] mode: $MODE"
  echo "[auth-config] hydra_url_match_mode: $OIDC_HYDRA_URL_MATCH_MODE"
  echo "[auth-config] admin_callbacks: $(join_csv ADMIN_CALLBACKS)"
  echo "[auth-config] dev_callbacks: $(join_csv DEV_CALLBACKS)"
  echo "[auth-config] org_callbacks: $(join_csv ORG_CALLBACKS)"
  echo "[auth-config] kratos_allowed_return_urls_count: ${#KRATOS_ALLOWED_RETURN_URLS[@]}"

  if [[ ${#WARNINGS[@]} -gt 0 ]]; then
    for message in "${WARNINGS[@]}"; do
      echo "[auth-config] WARN: $message"
    done
  fi
}

case "$MODE" in
  build)
    run_validation
    write_output
    print_summary
    echo "[auth-config] wrote: $OUTPUT_FILE"
    ;;
  validate)
    run_validation
    print_summary
    ;;
  verify)
    run_validation
    validate_compose_wiring
    verify_runtime_hydra_clients
    print_summary
    echo "[auth-config] compose wiring verified"
    ;;
  *)
    fail "Unsupported mode: $MODE (supported: build|validate|verify)"
    ;;
esac
