#!/bin/bash
# =============================================================================
# Admin Secrets - multi-backend secrets management
# =============================================================================
# Supports three backends: infisical (cloud), vault (age-encrypted), env (plaintext).
# Uses satellite ~/.admin/.env to resolve backend, paths, and Infisical config.
#
# Usage:
#   secrets KEY                    - Get single secret value
#   secrets -l, --list             - List all secret keys
#   secrets -e, --export           - Export all secrets (KEY=value format)
#   secrets -s, --source           - Source all secrets (eval $(secrets -s))
#   secrets --encrypt FILE         - Encrypt a plaintext file to vault
#   secrets --decrypt              - Decrypt and display all secrets
#   secrets --edit                 - Decrypt to temp, edit, re-encrypt
#   secrets --status               - Show vault/infisical status and paths
#   secrets --backend BACKEND      - Override backend for this invocation
#   secrets --migrate-to-infisical - Decrypt vault, push each key to Infisical
#   secrets -h, --help             - Show this help
#
# Backends (ADMIN_SECRETS_BACKEND):
#   infisical  - Infisical Cloud (primary for multi-device setups)
#   vault      - Age-encrypted vault.age (default, offline-capable)
#   env        - Plaintext .env file (legacy, not recommended)
#
# Files:
#   Key:       ~/.age/key.txt (never commit/sync this)
#   Vault:     $ADMIN_ROOT/vault.age (encrypted, git-safe)
#   Satellite: ~/.admin/.env (points to ADMIN_ROOT)
# =============================================================================

set -euo pipefail

# --- Resolve paths from satellite .env ---
SATELLITE_ENV="${HOME}/.admin/.env"

resolve_admin_root() {
    if [[ -n "${ADMIN_ROOT:-}" ]]; then
        echo "$ADMIN_ROOT"; return
    fi
    if [[ -f "$SATELLITE_ENV" ]]; then
        local root
        root=$(grep "^ADMIN_ROOT=" "$SATELLITE_ENV" 2>/dev/null | head -1 | cut -d'=' -f2-)
        if [[ -n "$root" ]]; then
            echo "$root"; return
        fi
    fi
    echo "${HOME}/.admin"
}

resolve_age_key() {
    if [[ -n "${AGE_KEY_PATH:-}" ]]; then
        echo "$AGE_KEY_PATH"; return
    fi
    if [[ -f "$SATELLITE_ENV" ]]; then
        local key_path
        key_path=$(grep "^AGE_KEY_PATH=" "$SATELLITE_ENV" 2>/dev/null | head -1 | cut -d'=' -f2-)
        if [[ -n "$key_path" ]]; then
            echo "$key_path"; return
        fi
    fi
    echo "${HOME}/.age/key.txt"
}

ADMIN_ROOT="$(resolve_admin_root)"
AGE_KEY="$(resolve_age_key)"
VAULT_FILE="${ADMIN_ROOT}/vault.age"

# --- Backend resolution ---
resolve_secrets_backend() {
    # CLI override takes priority (set by --backend flag)
    if [[ -n "${SECRETS_BACKEND_OVERRIDE:-}" ]]; then
        echo "$SECRETS_BACKEND_OVERRIDE"; return
    fi
    if [[ -n "${ADMIN_SECRETS_BACKEND:-}" ]]; then
        echo "$ADMIN_SECRETS_BACKEND"; return
    fi
    if [[ -f "$SATELLITE_ENV" ]]; then
        local backend
        backend=$(grep "^ADMIN_SECRETS_BACKEND=" "$SATELLITE_ENV" 2>/dev/null | head -1 | cut -d'=' -f2-)
        if [[ -n "$backend" ]]; then
            echo "$backend"; return
        fi
    fi
    echo "vault"
}

resolve_infisical_project_id() {
    if [[ -n "${INFISICAL_PROJECT_ID:-}" ]]; then
        echo "$INFISICAL_PROJECT_ID"; return
    fi
    if [[ -f "$SATELLITE_ENV" ]]; then
        grep "^INFISICAL_PROJECT_ID=" "$SATELLITE_ENV" 2>/dev/null | head -1 | cut -d'=' -f2-
    fi
}

resolve_infisical_env() {
    if [[ -n "${INFISICAL_ENVIRONMENT:-}" ]]; then
        echo "$INFISICAL_ENVIRONMENT"; return
    fi
    if [[ -f "$SATELLITE_ENV" ]]; then
        local env
        env=$(grep "^INFISICAL_ENVIRONMENT=" "$SATELLITE_ENV" 2>/dev/null | head -1 | cut -d'=' -f2-)
        if [[ -n "$env" ]]; then
            echo "$env"; return
        fi
    fi
    echo "prod"
}

SECRETS_BACKEND="$(resolve_secrets_backend)"

# --- Infisical operations ---
check_infisical() {
    if ! command -v infisical &>/dev/null; then
        echo "Error: infisical CLI not installed" >&2
        echo "Install: https://infisical.com/docs/cli/overview" >&2
        return 1
    fi
    local project_id
    project_id="$(resolve_infisical_project_id)"
    if [[ -z "$project_id" ]]; then
        echo "Error: INFISICAL_PROJECT_ID not set in satellite .env or environment" >&2
        return 1
    fi
    return 0
}

infisical_get_secret() {
    local key="$1"
    local project_id env_slug
    project_id="${INFISICAL_PROJECT_OVERRIDE:-$(resolve_infisical_project_id)}"
    env_slug="$(resolve_infisical_env)"
    local args=("secrets" "get" "$key" "--projectId" "$project_id" "--env" "$env_slug" "--plain")
    if [[ -n "${INFISICAL_PATH_OVERRIDE:-}" ]]; then
        args+=("--path" "$INFISICAL_PATH_OVERRIDE")
    fi
    infisical "${args[@]}" 2>/dev/null
}

infisical_list_secrets() {
    local project_id env_slug
    project_id="${INFISICAL_PROJECT_OVERRIDE:-$(resolve_infisical_project_id)}"
    env_slug="$(resolve_infisical_env)"
    local args=("secrets" "list" "--projectId" "$project_id" "--env" "$env_slug")
    if [[ -n "${INFISICAL_PATH_OVERRIDE:-}" ]]; then
        args+=("--path" "$INFISICAL_PATH_OVERRIDE")
    fi
    infisical "${args[@]}" 2>/dev/null | \
        tail -n +2 | awk '{print $2}' | sort
}

infisical_export_secrets() {
    local project_id env_slug
    project_id="${INFISICAL_PROJECT_OVERRIDE:-$(resolve_infisical_project_id)}"
    env_slug="$(resolve_infisical_env)"
    local args=("export" "--projectId" "$project_id" "--env" "$env_slug" "--format=dotenv")
    if [[ -n "${INFISICAL_PATH_OVERRIDE:-}" ]]; then
        args+=("--path" "$INFISICAL_PATH_OVERRIDE")
    fi
    infisical "${args[@]}" 2>/dev/null
}

# --- Fallback chain ---
# Try primary backend, fall back to vault, then env
get_secret_with_fallback() {
    local key="$1"
    local value=""

    case "$SECRETS_BACKEND" in
        infisical)
            if check_infisical 2>/dev/null; then
                value=$(infisical_get_secret "$key" 2>/dev/null || true)
            fi
            if [[ -z "$value" ]]; then
                # Fallback to vault
                if [[ -f "$VAULT_FILE" && -f "$AGE_KEY" ]]; then
                    value=$(decrypt_vault 2>/dev/null | grep "^${key}=" | cut -d'=' -f2- | head -1 || true)
                fi
            fi
            ;;
        vault)
            value=$(decrypt_vault | grep "^${key}=" | cut -d'=' -f2- | head -1 || true)
            ;;
        env)
            local master_env="${ADMIN_ROOT}/.env"
            if [[ -f "$master_env" ]]; then
                value=$(grep "^${key}=" "$master_env" | cut -d'=' -f2- | head -1 || true)
            fi
            ;;
    esac

    if [[ -z "$value" ]]; then
        echo "Error: Secret '$key' not found (backend: $SECRETS_BACKEND)" >&2
        return 1
    fi
    echo "$value"
}

list_secrets_with_fallback() {
    case "$SECRETS_BACKEND" in
        infisical)
            if check_infisical 2>/dev/null; then
                infisical_list_secrets
            else
                list_secrets 2>/dev/null || true
            fi
            ;;
        vault)
            list_secrets
            ;;
        env)
            local master_env="${ADMIN_ROOT}/.env"
            if [[ -f "$master_env" ]]; then
                grep -v '^\s*#' "$master_env" | grep -v '^\s*$' | cut -d'=' -f1 | sort
            fi
            ;;
    esac
}

export_secrets_with_fallback() {
    case "$SECRETS_BACKEND" in
        infisical)
            if check_infisical 2>/dev/null; then
                infisical_export_secrets
            else
                export_secrets 2>/dev/null || true
            fi
            ;;
        vault)
            export_secrets
            ;;
        env)
            local master_env="${ADMIN_ROOT}/.env"
            if [[ -f "$master_env" ]]; then
                grep -v '^\s*#' "$master_env" | grep -v '^\s*$' | grep '='
            fi
            ;;
    esac
}

migrate_to_infisical() {
    echo "Migrating vault secrets to Infisical..."
    check_key
    check_vault
    check_infisical || exit 1

    local project_id env_slug
    project_id="$(resolve_infisical_project_id)"
    env_slug="$(resolve_infisical_env)"

    local count=0
    local key value
    while IFS= read -r line; do
        [[ "$line" =~ ^[[:space:]]*# ]] && continue
        [[ -z "${line// }" ]] && continue
        [[ "$line" != *"="* ]] && continue

        key="${line%%=*}"
        value="${line#*=}"

        echo -n "  $key... "
        if infisical secrets set "$key=$value" --projectId "$project_id" --env "$env_slug" >/dev/null 2>&1; then
            echo "OK"
            count=$((count + 1))
        else
            echo "FAILED"
        fi
    done < <(decrypt_vault)

    echo ""
    echo "Migrated $count secrets to Infisical (project: $project_id, env: $env_slug)"
    echo ""
    echo "Next steps:"
    echo "  1. Verify: secrets --backend infisical --list"
    echo "  2. Update satellite .env: ADMIN_SECRETS_BACKEND=infisical"
    echo "  3. Keep vault.age as offline fallback"
}

# --- Validation ---
check_key() {
    if [[ ! -f "$AGE_KEY" ]]; then
        echo "Error: Age key not found at $AGE_KEY" >&2
        echo "Generate one with: age-keygen -o ~/.age/key.txt" >&2
        exit 1
    fi
}

check_vault() {
    if [[ ! -f "$VAULT_FILE" ]]; then
        echo "Error: Vault not found at $VAULT_FILE" >&2
        echo "Create one with: secrets --encrypt /path/to/plaintext.env" >&2
        echo "Or migrate existing: migrate-to-vault.sh" >&2
        exit 1
    fi
}

# --- Core operations ---
decrypt_vault() {
    check_key
    check_vault
    age -d -i "$AGE_KEY" "$VAULT_FILE" 2>/dev/null
}

get_secret() {
    local key="$1"
    local value
    value=$(decrypt_vault | grep "^${key}=" | cut -d'=' -f2- | head -1 || true)
    if [[ -z "$value" ]]; then
        echo "Error: Secret '$key' not found in vault" >&2
        return 1
    fi
    echo "$value"
}

list_secrets() {
    decrypt_vault | grep -v '^#' | grep -v '^[[:space:]]*$' | cut -d'=' -f1 | sort
}

export_secrets() {
    decrypt_vault | grep -v '^#' | grep -v '^[[:space:]]*$' | grep '='
}

encrypt_file() {
    local input_file="$1"
    local output_file="${2:-$VAULT_FILE}"

    if [[ ! -f "$input_file" ]]; then
        echo "Error: File not found: $input_file" >&2
        exit 1
    fi

    check_key

    local public_key
    public_key=$(age-keygen -y "$AGE_KEY")

    age -e -r "$public_key" -a -o "$output_file" "$input_file"
    chmod 600 "$output_file"

    local count
    count=$(grep -c '=' "$input_file" 2>/dev/null || echo 0)
    echo "Encrypted: $input_file -> $output_file ($count secrets)"
}

edit_vault() {
    check_key
    check_vault

    local tmp
    tmp=$(mktemp /tmp/vault-edit.XXXXXX)
    trap 'rm -f "$tmp"' EXIT

    # Decrypt to temp file
    age -d -i "$AGE_KEY" "$VAULT_FILE" > "$tmp"

    # Open in editor
    "${EDITOR:-vi}" "$tmp"

    # Re-encrypt
    local public_key
    public_key=$(age-keygen -y "$AGE_KEY")
    age -e -r "$public_key" -a -o "$VAULT_FILE" "$tmp"

    # Secure delete temp
    rm -f "$tmp"
    trap - EXIT

    echo "Vault updated."
}

show_status() {
    echo "Admin Secrets Status"
    echo "────────────────────────────────────"
    echo "Backend:     $SECRETS_BACKEND"
    echo ""

    # Infisical status
    if [[ "$SECRETS_BACKEND" == "infisical" ]] || command -v infisical &>/dev/null; then
        echo "Infisical:"
        if command -v infisical &>/dev/null; then
            local project_id env_slug
            project_id="$(resolve_infisical_project_id)"
            env_slug="$(resolve_infisical_env)"
            echo "  CLI:       installed"
            echo "  Project:   ${project_id:-not set}"
            echo "  Env:       ${env_slug}"
            if [[ -n "$project_id" ]]; then
                if infisical secrets list --projectId "$project_id" --env "$env_slug" >/dev/null 2>&1; then
                    echo "  Auth:      OK"
                else
                    echo "  Auth:      FAILED (run: infisical login)"
                fi
            fi
        else
            echo "  CLI:       NOT INSTALLED"
        fi
        echo ""
    fi

    # Vault status
    echo "Age key:     $AGE_KEY"
    if [[ -f "$AGE_KEY" ]]; then
        echo "  Status:    OK ($(stat -c '%A' "$AGE_KEY" 2>/dev/null || stat -f '%Sp' "$AGE_KEY" 2>/dev/null))"
        echo "  Public:    $(age-keygen -y "$AGE_KEY" 2>/dev/null)"
    else
        echo "  Status:    MISSING"
    fi
    echo ""
    echo "Vault:       $VAULT_FILE"
    if [[ -f "$VAULT_FILE" ]]; then
        echo "  Status:    OK ($(stat -c '%s' "$VAULT_FILE" 2>/dev/null || stat -f '%z' "$VAULT_FILE" 2>/dev/null) bytes)"
        local count
        count=$(decrypt_vault 2>/dev/null | grep -c '=' || echo "?")
        echo "  Secrets:   $count"
    else
        echo "  Status:    NOT CREATED"
    fi
    echo ""
    echo "Satellite:   $SATELLITE_ENV"
    if [[ -f "$SATELLITE_ENV" ]]; then
        echo "  ADMIN_ROOT=$ADMIN_ROOT"
        local vault_flag
        vault_flag=$(grep "^ADMIN_VAULT=" "$SATELLITE_ENV" 2>/dev/null | head -1 | cut -d'=' -f2- || echo "not set")
        echo "  ADMIN_VAULT=${vault_flag:-not set}"
        local backend_flag
        backend_flag=$(grep "^ADMIN_SECRETS_BACKEND=" "$SATELLITE_ENV" 2>/dev/null | head -1 | cut -d'=' -f2- || echo "not set")
        echo "  ADMIN_SECRETS_BACKEND=${backend_flag:-not set}"
    else
        echo "  Status:    MISSING"
    fi
}

# --- Resolve project slug to ID (for --project flag) ---
PROJECTS_FILE="${ADMIN_ROOT}/config/infisical-projects.json"

resolve_project_slug() {
    local slug="$1"
    if [[ -f "$PROJECTS_FILE" ]] && command -v jq &>/dev/null; then
        local pid
        pid=$(jq -r ".projects[\"$slug\"].id // empty" "$PROJECTS_FILE" 2>/dev/null)
        if [[ -n "$pid" ]]; then echo "$pid"; return 0; fi
    fi
    # If slug looks like a UUID, use directly
    if [[ "$slug" =~ ^[0-9a-f-]{36}$ ]]; then echo "$slug"; return 0; fi
    # Env var fallback: INFISICAL_PROJECT_ID_ADMIN_OPERATOR etc.
    local env_var="INFISICAL_PROJECT_ID_$(echo "$slug" | tr '[:lower:]-' '[:upper:]_')"
    if [[ -n "${!env_var:-}" ]]; then echo "${!env_var}"; return 0; fi
    echo "Error: Cannot resolve project slug '$slug'" >&2
    return 1
}

# --- Parse prefix flags (must come before other args) ---
INFISICAL_PROJECT_OVERRIDE=""
INFISICAL_PATH_OVERRIDE=""

while [[ "${1:-}" == --backend || "${1:-}" == --path || "${1:-}" == --project ]]; do
    case "$1" in
        --backend)
            if [[ -z "${2:-}" ]]; then
                echo "Usage: secrets --backend <infisical|vault|env> COMMAND" >&2
                exit 1
            fi
            SECRETS_BACKEND_OVERRIDE="$2"
            SECRETS_BACKEND="$2"
            shift 2
            ;;
        --path)
            if [[ -z "${2:-}" ]]; then
                echo "Usage: secrets --path <FOLDER> COMMAND" >&2
                exit 1
            fi
            INFISICAL_PATH_OVERRIDE="$2"
            shift 2
            ;;
        --project)
            if [[ -z "${2:-}" ]]; then
                echo "Usage: secrets --project <SLUG|ID> COMMAND" >&2
                exit 1
            fi
            local_project_id=$(resolve_project_slug "$2") || exit 1
            INFISICAL_PROJECT_OVERRIDE="$local_project_id"
            shift 2
            ;;
    esac
done

# --- Main command handler ---
case "${1:-}" in
    -l|--list)
        list_secrets_with_fallback
        ;;
    -e|--export)
        export_secrets_with_fallback
        ;;
    -s|--source)
        export_secrets_with_fallback | while IFS= read -r line; do
            echo "export $line"
        done
        ;;
    --decrypt)
        decrypt_vault
        ;;
    --encrypt)
        if [[ -z "${2:-}" ]]; then
            echo "Usage: secrets --encrypt FILE [OUTPUT]" >&2
            echo "  FILE:   Plaintext .env file to encrypt" >&2
            echo "  OUTPUT: Output path (default: $VAULT_FILE)" >&2
            exit 1
        fi
        encrypt_file "$2" "${3:-$VAULT_FILE}"
        ;;
    --edit)
        edit_vault
        ;;
    --status)
        show_status
        ;;
    --migrate-to-infisical)
        migrate_to_infisical
        ;;
    -h|--help)
        echo "Admin Secrets - multi-backend secrets management"
        echo ""
        echo "Backend: $SECRETS_BACKEND (set via ADMIN_SECRETS_BACKEND or --backend)"
        echo ""
        echo "Usage:"
        echo "  secrets KEY                    - Get value for KEY"
        echo "  secrets -l, --list             - List all secret keys"
        echo "  secrets -e, --export           - Export all secrets (KEY=value format)"
        echo "  secrets -s, --source           - Output for sourcing (eval \$(secrets -s))"
        echo "  secrets --decrypt              - Decrypt and display vault secrets"
        echo "  secrets --encrypt FILE [OUT]   - Encrypt plaintext file to vault"
        echo "  secrets --edit                 - Edit vault in \$EDITOR"
        echo "  secrets --status               - Show backend status and paths"
        echo "  secrets --backend BACKEND CMD  - Override backend for this invocation"
        echo "  secrets --migrate-to-infisical - Push vault secrets to Infisical"
        echo "  secrets -h, --help             - Show this help"
        echo ""
        echo "Backends:"
        echo "  infisical  - Infisical Cloud (recommended for multi-device)"
        echo "  vault      - Age-encrypted vault.age (default, offline-capable)"
        echo "  env        - Plaintext .env file (legacy)"
        echo ""
        echo "Examples:"
        echo "  secrets HCLOUD_TOKEN                       - Get from current backend"
        echo "  secrets --backend vault HCLOUD_TOKEN       - Force vault backend"
        echo "  secrets --migrate-to-infisical             - Move vault -> Infisical"
        echo "  eval \$(secrets -s)                         - Load all as env vars"
        echo ""
        echo "Files:"
        echo "  Key:       $AGE_KEY"
        echo "  Vault:     $VAULT_FILE"
        echo "  Satellite: $SATELLITE_ENV"
        ;;
    "")
        echo "Usage: secrets KEY | -l | -e | -s | --decrypt | --encrypt FILE | --edit | --status | --backend | -h" >&2
        exit 1
        ;;
    -*)
        echo "Error: Unknown option '$1'" >&2
        echo "Run 'secrets --help' for usage" >&2
        exit 1
        ;;
    *)
        get_secret_with_fallback "$1"
        ;;
esac
