#!/bin/bash
# Git credential helper that fetches fresh tokens from server
# Only responds for repo URLs in CODER_MANAGED_REPOS; others fall through to store helper
#
# This helper is configured first in the credential helper chain:
#   [credential]
#       helper = /usr/local/bin/coder-git-credential-helper
#       helper = store
#
# Behavior:
#   - get: Check if URL is managed; if so, return credentials (from cache or server)
#          If not managed, exit silently to fall through to store helper
#   - store/erase: Ignore (we don't persist, store helper handles non-managed repos)

set -o pipefail

OPERATION="$1"
CACHE_DIR="/home/coder/.cache/coder-git-credentials"
CACHE_FILE="$CACHE_DIR/cache.json"
SERVER_URL="${CODER_CREDENTIAL_SERVER:-}"
CONTAINER_TOKEN="${CODER_CONTAINER_TOKEN:-}"
MANAGED_REPOS="${CODER_MANAGED_REPOS:-[]}"
CACHE_BUFFER_SECONDS=300  # Refresh 5 minutes before expiry

# Log function (only logs when CODER_CREDENTIAL_DEBUG is set)
debug_log() {
    if [ -n "${CODER_CREDENTIAL_DEBUG:-}" ]; then
        echo "[coderflow-git-credential-helper] $*" >&2
    fi
}

# Normalize a URL for comparison (strip .git suffix, lowercase host)
normalize_url() {
    local url="$1"
    # Remove trailing .git
    url="${url%.git}"
    # Remove trailing slash
    url="${url%/}"
    # Lowercase the host part (everything up to the third slash)
    # e.g., https://GITHUB.COM/owner/repo -> https://github.com/owner/repo
    echo "$url" | sed -E 's|^(https?://[^/]+)|\L\1|'
}

# Check if a URL is in the managed repos list
is_managed_repo() {
    local url="$1"
    local normalized_url
    normalized_url=$(normalize_url "$url")

    # Check if URL is in MANAGED_REPOS array
    # MANAGED_REPOS is a JSON array of URLs
    if echo "$MANAGED_REPOS" | jq -e --arg url "$normalized_url" 'map(. | gsub("\\.git$"; "") | gsub("/$"; "")) | index($url) != null' > /dev/null 2>&1; then
        return 0
    fi
    return 1
}

# Get cached credentials for a URL
get_cached_credentials() {
    local url="$1"
    local normalized_url
    normalized_url=$(normalize_url "$url")

    # Check if cache file exists
    if [ ! -f "$CACHE_FILE" ]; then
        return 1
    fi

    # Try to get cached entry for this URL
    local cached_entry
    cached_entry=$(jq -r --arg url "$normalized_url" '.[$url] // empty' "$CACHE_FILE" 2>/dev/null)

    if [ -z "$cached_entry" ] || [ "$cached_entry" = "null" ]; then
        debug_log "Cache miss for $normalized_url"
        return 1
    fi

    # Check if cache is still valid (not expired)
    local expires_at
    expires_at=$(echo "$cached_entry" | jq -r '.expires_at // empty')

    if [ -z "$expires_at" ]; then
        debug_log "Cache entry missing expires_at for $normalized_url"
        return 1
    fi

    # Convert ISO timestamp to epoch seconds
    local expires_epoch
    expires_epoch=$(date -d "$expires_at" +%s 2>/dev/null)

    if [ -z "$expires_epoch" ]; then
        debug_log "Failed to parse expires_at: $expires_at"
        return 1
    fi

    local now_epoch
    now_epoch=$(date +%s)

    # Check if expired (with buffer)
    local effective_expires=$((expires_epoch - CACHE_BUFFER_SECONDS))
    if [ "$now_epoch" -ge "$effective_expires" ]; then
        debug_log "Cache expired for $normalized_url (expires: $expires_at, buffer: ${CACHE_BUFFER_SECONDS}s)"
        return 1
    fi

    # Cache is valid - extract credentials
    local username password
    username=$(echo "$cached_entry" | jq -r '.username // empty')
    password=$(echo "$cached_entry" | jq -r '.password // empty')

    if [ -z "$username" ] || [ -z "$password" ]; then
        debug_log "Cache entry missing username or password for $normalized_url"
        return 1
    fi

    debug_log "Cache hit for $normalized_url (expires: $expires_at)"
    echo "$username"
    echo "$password"
    return 0
}

# Update cache with new credentials
update_cache() {
    local url="$1"
    local username="$2"
    local password="$3"
    local expires_at="$4"
    local normalized_url
    normalized_url=$(normalize_url "$url")

    # Ensure cache directory exists with proper permissions
    mkdir -p "$CACHE_DIR"
    chmod 700 "$CACHE_DIR"

    # Read existing cache or start fresh
    local existing_cache="{}"
    if [ -f "$CACHE_FILE" ]; then
        existing_cache=$(cat "$CACHE_FILE" 2>/dev/null || echo "{}")
        # Validate it's valid JSON
        if ! echo "$existing_cache" | jq . > /dev/null 2>&1; then
            existing_cache="{}"
        fi
    fi

    # Add/update entry for this URL
    local new_cache
    new_cache=$(echo "$existing_cache" | jq --arg url "$normalized_url" \
        --arg username "$username" \
        --arg password "$password" \
        --arg expires_at "$expires_at" \
        '.[$url] = {username: $username, password: $password, expires_at: $expires_at}')

    # Write to cache file with secure permissions
    echo "$new_cache" > "$CACHE_FILE"
    chmod 600 "$CACHE_FILE"

    debug_log "Cache updated for $normalized_url (expires: $expires_at)"
}

# Fetch credentials from server
fetch_from_server() {
    local url="$1"

    # Check if server is configured
    if [ -z "$SERVER_URL" ]; then
        debug_log "No CODER_CREDENTIAL_SERVER configured"
        return 1
    fi

    if [ -z "$CONTAINER_TOKEN" ]; then
        debug_log "No CODER_CONTAINER_TOKEN configured"
        return 1
    fi

    # URL-encode the repo URL for the query parameter
    local encoded_url
    encoded_url=$(printf '%s' "$url" | jq -sRr @uri)

    local api_url="${SERVER_URL}/api/git/credentials?repo_url=${encoded_url}"

    debug_log "Fetching credentials from: $api_url"

    # Make HTTP request to server.
    # -sS keeps curl quiet on success but still emits transport errors (TLS,
    # DNS, refused, timeout) on stderr; capture that to a temp file so we can
    # surface it unconditionally without polluting the success path.
    local response http_code curl_exit curl_stderr curl_stderr_file
    curl_stderr_file=$(mktemp)
    response=$(curl -sS -w "\n%{http_code}" \
        -H "Authorization: Bearer $CONTAINER_TOKEN" \
        -H "Accept: application/json" \
        --connect-timeout 10 \
        --max-time 30 \
        "$api_url" 2>"$curl_stderr_file")
    curl_exit=$?
    curl_stderr=$(cat "$curl_stderr_file" 2>/dev/null)
    rm -f "$curl_stderr_file"

    # Extract HTTP status code (last line)
    http_code=$(echo "$response" | tail -n1)
    # Extract response body (all but last line)
    response=$(echo "$response" | sed '$d')

    # Curl transport failure: surface the underlying cause to stderr without
    # requiring CODER_CREDENTIAL_DEBUG, so customers can self-diagnose issues
    # like an untrusted CA on a self-signed credential server.
    if [ "$curl_exit" -ne 0 ]; then
        echo "[coderflow-git-credential-helper] Failed to reach credential server at $api_url" >&2
        echo "[coderflow-git-credential-helper] curl exit code: $curl_exit" >&2
        if [ -n "$curl_stderr" ]; then
            while IFS= read -r line; do
                echo "[coderflow-git-credential-helper] $line" >&2
            done <<< "$curl_stderr"
        fi
        return 1
    fi

    if [ "$http_code" != "200" ]; then
        debug_log "Server returned HTTP $http_code"
        debug_log "Response: $response"

        # Surface actionable errors to the user via stderr
        local error_code error_message
        error_code=$(echo "$response" | jq -r '.error_code // empty' 2>/dev/null)
        error_message=$(echo "$response" | jq -r '.message // empty' 2>/dev/null)

        if [ "$error_code" = "container_token_expired" ]; then
            echo "[coderflow-git-credential-helper] This task's Git credentials have expired." >&2
            echo "[coderflow-git-credential-helper] Start a new task to continue making changes." >&2
        elif [ "$error_code" = "oauth_token_expired" ]; then
            echo "[coderflow-git-credential-helper] Git authentication has expired for this repository." >&2
            echo "[coderflow-git-credential-helper] Please reconnect your Git account in Settings > Git Connections." >&2
        elif [ "$error_code" = "user_not_connected" ]; then
            echo "[coderflow-git-credential-helper] Git account not connected for this repository." >&2
            echo "[coderflow-git-credential-helper] Please connect your account in Settings > Git Connections." >&2
        elif [ -n "$error_message" ]; then
            echo "[coderflow-git-credential-helper] Credential error: $error_message" >&2
        else
            echo "[coderflow-git-credential-helper] Credential server returned HTTP $http_code from $api_url" >&2
            if [ -n "$response" ]; then
                echo "[coderflow-git-credential-helper] Response: $response" >&2
            fi
        fi

        return 1
    fi

    # Parse response JSON
    local username password expires_at
    username=$(echo "$response" | jq -r '.username // empty')
    password=$(echo "$response" | jq -r '.password // empty')
    expires_at=$(echo "$response" | jq -r '.expires_at // empty')

    if [ -z "$username" ] || [ -z "$password" ]; then
        debug_log "Server response missing username or password"
        return 1
    fi

    debug_log "Got credentials from server (expires: $expires_at)"

    # Update cache with new credentials
    if [ -n "$expires_at" ]; then
        update_cache "$url" "$username" "$password" "$expires_at"
    fi

    echo "$username"
    echo "$password"
    return 0
}

# Handle 'get' operation
handle_get() {
    local protocol="" host="" path_part=""

    # Parse stdin (git credential protocol format)
    while IFS='=' read -r key value; do
        case "$key" in
            protocol) protocol="$value" ;;
            host) host="$value" ;;
            path) path_part="$value" ;;
        esac
    done

    debug_log "Received: protocol=$protocol host=$host path=$path_part"

    # Validate we have required fields
    if [ -z "$protocol" ] || [ -z "$host" ]; then
        debug_log "Missing protocol or host, exiting"
        exit 0
    fi

    # Reconstruct URL (strip .git suffix for matching)
    local repo_url="${protocol}://${host}/${path_part%.git}"

    debug_log "Reconstructed URL: $repo_url"

    # Check if this repo is managed by us
    if ! is_managed_repo "$repo_url"; then
        debug_log "Not a managed repo, falling through to next helper"
        exit 0
    fi

    debug_log "Repo is managed, looking for credentials"

    # Try to get cached credentials
    local cached_result
    cached_result=$(get_cached_credentials "$repo_url")

    if [ -n "$cached_result" ]; then
        local username password
        username=$(echo "$cached_result" | head -1)
        password=$(echo "$cached_result" | tail -1)

        echo "username=$username"
        echo "password=$password"
        exit 0
    fi

    # Cache miss - fetch from server
    local server_result
    server_result=$(fetch_from_server "$repo_url")

    if [ -n "$server_result" ]; then
        local username password
        username=$(echo "$server_result" | head -1)
        password=$(echo "$server_result" | tail -1)

        echo "username=$username"
        echo "password=$password"
        exit 0
    fi

    # Failed to get credentials - exit silently to fall through
    debug_log "Failed to get credentials, falling through to next helper"
    exit 0
}

# Main operation switch
case "$OPERATION" in
    get)
        handle_get
        ;;
    store|erase)
        # Ignore store/erase - we don't persist credentials
        # The store helper in the chain handles non-managed repos
        exit 0
        ;;
    *)
        # Unknown operation - exit silently
        exit 0
        ;;
esac
