# shellcheck shell=sh        disable=SC2039,SC1090,SC3043,SC2263
# x-cmd cve lib/data/fetch — HTTP fetch primitives for the cache layer.
#
# We do NOT use `x ccmd 1h` for memoization here. x ccmd caches
# command stdout, which is empty for `curl -o FILE` (output goes to a
# file, not stdout). On a partial download (curl killed by
# --speed-time), x ccmd happily caches the empty stdout as "successful
# empty result" and returns it on the next call without ever re-trying
# the download. The disk cache + TTL in `data/_index` is enough
# memoization for our use case.
#
# We download .xz files as-is and STREAM them via `xz -dc` when reading.
# No decompression to disk — saves ~7-10 MB per year × 28 years of
# duplicate uncompressed data. Callers do:
#
#   xz -dc "$fp" | awk -F '\t' '...'

# Build the mirror URL for a github.com asset.
#   https://github.com/<rest>  ->  https://${host}/${prefix}/<rest>
# Disable with X_CMD_CVE_NO_MIRROR=1.
# Defaults: host=eget.ljh.sh, prefix=gh (same as x eget's mirror).
___x_cmd_cve_data_mirror_url(){
    local url="$1"
    case "${X_CMD_CVE_NO_MIRROR:-}" in
        1) return 1 ;;
    esac
    case "$url" in
        https://github.com/*) ;;
        *) return 1 ;;
    esac
    local host="${X_CMD_CVE_MIRROR_HOST:-eget.ljh.sh}"
    local prefix="${X_CMD_CVE_MIRROR_PATH:-gh}"
    case "$prefix" in
        /*) prefix="${prefix#/}" ;;
    esac
    [ -z "$prefix" ] && prefix="gh"
    printf 'https://%s/%s/%s' "$host" "$prefix" "${url#https://github.com/}"
}

# Fetch a single URL to a file (the destination path is exactly what
# the caller passes — we don't rename, don't strip extensions, don't
# run any post-processing). For our use case the URL ends in .tsv.xz
# and the caller writes it to <cache>/cve-YYYY.tsv.xz. On github.com
# stall/error, retry once via the eget.ljh.sh mirror.
___x_cmd_cve_data_fetch_one(){
    local url="$1"
    local fp="$2"
    local speed_limit="${X_CMD_CVE_SPEED_LIMIT:-50000}"
    local speed_time="${X_CMD_CVE_SPEED_TIME:-2}"

    # Tell the user something is happening — these files can be
    # 18 MB compressed and stall-prone on github.com, so silence
    # here looks like a hang.
    cve:info "downloading $(basename "${fp}") ..."

    local err="${fp}.err"
    if ! ___x_cmd curl --fail --location --silent --show-error \
            --connect-timeout 15 --max-time 120 \
            --speed-limit "$speed_limit" \
            --speed-time  "$speed_time" \
            "$url" -o "$fp" 2>"$err"; then
        # Try the mirror fallback for github.com URLs.
        local mirror
        if mirror="$(___x_cmd_cve_data_mirror_url "$url")"; then
            cve:info "github.com stalled — retrying $(basename "${fp}") via mirror"
            if ! ___x_cmd curl --fail --location --silent --show-error \
                    --connect-timeout 15 --max-time 120 \
                    --speed-limit "$speed_limit" \
                    --speed-time  "$speed_time" \
                    "$mirror" -o "$fp" 2>"$err"; then
                cve:warn "download failed: $(cat "$err" 2>/dev/null)"
                rm -f "$fp" "$err"
                return 1
            fi
        else
            cve:warn "download failed: $(cat "$err" 2>/dev/null)"
            rm -f "$fp" "$err"
            return 1
        fi
    fi
    rm -f "$err"

    # Sanity: a 0-byte file is no good (e.g. previous failed fetch
    # left a touch). Drop and fail.
    if [ ! -s "$fp" ]; then
        rm -f "$fp"
        return 1
    fi

    return 0
}

# Fetch N URLs in parallel using curl --http2 -Z (HTTP/2 multiplexing,
# multiple streams per TCP connection). Falls back gracefully when the
# underlying libcurl lacks --http2 / --parallel support.
#
# Args: out_dir, then pairs of (url path) ... (url1 out1 url2 out2 ...)
# Returns the number of failed downloads in the global ret_failures.
___x_cmd_cve_data_fetch_parallel(){
    local out_dir="$1"; shift
    ___x_cmd mkdirp "$out_dir"

    local ret_failures=0
    local pairs=""
    local urls=()

    while [ $# -gt 0 ]; do
        local url="$1"; local fp="$2"
        urls+=("$url")
        pairs="${pairs}url = \"${url}\"
output = \"${fp}\"

"
        shift 2
    done

    [ "${#urls[@]}" -gt 0 ] || return 0

    local help
    help="$(___x_cmd curl --help all 2>/dev/null || true)"
    local curl_args=(--fail --location --silent --show-error
                     --connect-timeout 15 --max-time 120)

    if printf '%s\n' "$help" | grep -q -- '--http2 '; then
        curl_args=(--http2 "${curl_args[@]}")
    fi
    if printf '%s\n' "$help" | grep -q -- '--parallel'; then
        curl_args=(--parallel --parallel-max 4 "${curl_args[@]}")
    fi

    local cfg
    cfg="$(mktemp "${TMPDIR:-/tmp}/cve-curl-$$-XXXXXX.cfg")"
    printf '%s' "$pairs" > "$cfg"

    if [ "${#curl_args[@]}" -gt 5 ]; then
        ___x_cmd curl "${curl_args[@]}" --config "$cfg" \
        || ret_failures=$?
    else
        local url fp i=0
        for url in "${urls[@]}"; do
            fp="$(printf '%s' "$url" | sed 's#.*/##')"
            ___x_cmd_cve_data_fetch_one "$url" "$out_dir/$fp" \
            || ret_failures=$((ret_failures + 1))
        done
    fi
    rm -f "$cfg"
    return "$ret_failures"
}

# Batch-fetch multiple years in parallel via background jobs (since
# each asset is independent and curl --parallel can't run a per-file
# post-processing step).
# Args: <tag>, then year1 [year2 ...]
#
# We fan out into background jobs so multi-year fetches overlap. Each
# job inherits X_CMD_CVE_FORCE from the caller — so a plain
# `x cve info CVE-2024-0001 CVE-2025-0001` will skip the network for
# any year file already fresh in cache. Only an explicit
# `x cve update` (which sets X_CMD_CVE_FORCE=1 before calling here)
# triggers a refetch.
___x_cmd_cve_data_ensure_years(){
    local tag="${1:-${X_CMD_CVE_TAG:-$___X_CMD_CVE_DATA_DEFAULT_TAG}}"; shift
    [ $# -gt 0 ] || return 0
    ___x_cmd_cve_data_mkdir "$tag"

    local pids=""
    local year
    for year in "$@"; do
        (
            ___x_cmd_cve_data_ensure_year "$year" "$tag"
        ) &
        pids="${pids:+${pids} }$!"
    done

    local rc=0
    local pid
    for pid in $pids; do
        wait "$pid" || rc=$?
    done
    return "$rc"
}
