#!/usr/bin/env bash
# osdl — OS Distribution Mirror Gateway CLI bridge
# Channel: os.webservice.digital
# Pure bash + curl + sha256sum/sha512sum. Auditable. No heavy deps.
#
# This tool never hosts ISOs. It only uses official URLs and hashes
# published by the gateway metadata API.
set -euo pipefail

OSDL_VERSION="1.0.0"
OSDL_DEFAULT_API="${OSDL_API:-https://os.webservice.digital/api/v1/latest.json}"
OSDL_USER_AGENT="osdl/${OSDL_VERSION} (+https://os.webservice.digital)"
OSDL_CACHE_DIR="${OSDL_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/osdl}"
OSDL_CACHE_TTL="${OSDL_CACHE_TTL:-300}"

# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------
die()  { printf 'osdl: error: %s\n' "$*" >&2; exit 1; }
warn() { printf 'osdl: warning: %s\n' "$*" >&2; }
info() { printf 'osdl: %s\n' "$*" >&2; }

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

usage() {
  cat <<'EOF'
osdl — Linux distribution ISO metadata / download / verify bridge
        Channel: os.webservice.digital

USAGE
  osdl list
  osdl info <distro>
  osdl get <distro> [--verify] [--print] [--output <path>] [--variant <n|filename>]
  osdl verify <file> <distro> [--variant <n|filename>]
  osdl status
  osdl help
  osdl version

OPTIONS
  --verify          After download, verify SHA256/SHA512 against gateway metadata
  --print           Print commands only; do not execute download
  --output <path>   Output path for ISO (default: ./<filename>)
  --variant <id>    Use additional ISO by 1-based index or exact filename
  --api <url>       Override metadata API URL
  --refresh         Bypass local metadata cache

ENVIRONMENT
  OSDL_API          Metadata endpoint (default: https://os.webservice.digital/api/v1/latest.json)
  OSDL_CACHE_DIR    Cache directory for metadata JSON
  OSDL_CACHE_TTL    Cache TTL seconds (default: 300)

SECURITY NOTES
  • Downloads always use the official URLs published by the gateway.
  • SHA verification confirms bit-identity with published digests only.
  • You remain responsible for GPG verification of official checksum files.
  • This gateway does not host ISO binaries and is not an official mirror.

EXAMPLES
  osdl list
  osdl info debian
  osdl get ubuntu --print
  osdl get rocky --verify --output /var/tmp/rocky-minimal.iso
  osdl verify ./debian-13.6.0-amd64-netinst.iso debian
EOF
}

# ---------------------------------------------------------------------------
# metadata
# ---------------------------------------------------------------------------
fetch_metadata() {
  local api="$1" refresh="${2:-0}"
  need_cmd curl
  mkdir -p "$OSDL_CACHE_DIR"
  local cache_file="$OSDL_CACHE_DIR/latest.json"
  local now age

  if [[ "$refresh" != "1" && -f "$cache_file" ]]; then
    now=$(date +%s)
    age=$(( now - $(stat -c %Y "$cache_file" 2>/dev/null || stat -f %m "$cache_file") ))
    if (( age < OSDL_CACHE_TTL )); then
      cat "$cache_file"
      return 0
    fi
  fi

  local tmp
  tmp=$(mktemp)
  if ! curl -fsSL -A "$OSDL_USER_AGENT" --connect-timeout 15 --max-time 60 \
      -H 'Accept: application/json' "$api" -o "$tmp"; then
    rm -f "$tmp"
    if [[ -f "$cache_file" ]]; then
      warn "live fetch failed; using stale cache"
      cat "$cache_file"
      return 0
    fi
    die "failed to fetch metadata from $api"
  fi

  # minimal JSON sanity
  if ! grep -q '"distributions"' "$tmp"; then
    rm -f "$tmp"
    die "metadata payload missing distributions"
  fi

  mv "$tmp" "$cache_file"
  cat "$cache_file"
}

# Prefer python3 for robust JSON; fall back to a limited pure-bash path is not worth it.
json_query() {
  local json="$1" query="$2"
  need_cmd python3
  OSDL_JSON="$json" OSDL_QUERY="$query" python3 - <<'PY'
import json, os, sys
data = json.loads(os.environ["OSDL_JSON"])
q = os.environ["OSDL_QUERY"]

def find_distro(needle):
    needle = needle.lower()
    for d in data.get("distributions", []):
        if d.get("id", "").lower() == needle:
            return d
        for a in d.get("aliases") or []:
            if str(a).lower() == needle:
                return d
    return None

if q == "list":
    for d in data.get("distributions", []):
        code = d.get("codename") or ""
        print(f"{d.get('id','?'):<22} {d.get('name','?'):<18} {d.get('version','?'):<14} {code}")
elif q == "status":
    st = data.get("status") or {}
    print(f"channel={data.get('channel','')}")
    print(f"status={st.get('state','')}")
    print(f"message={st.get('message','')}")
    print(f"last_metadata_sync_utc={data.get('last_metadata_sync_utc','')}")
    print(f"generated_utc={data.get('generated_utc','')}")
elif q.startswith("info:"):
    distro = q.split(":", 1)[1]
    d = find_distro(distro)
    if not d:
        sys.exit(2)
    p = d.get("primary_iso") or {}
    print(f"id={d.get('id')}")
    print(f"name={d.get('name')}")
    print(f"version={d.get('version')}")
    print(f"codename={d.get('codename') or ''}")
    print(f"arch={d.get('arch') or ''}")
    print(f"official_page={d.get('official_page') or ''}")
    print(f"filename={p.get('filename') or ''}")
    print(f"url={p.get('url') or ''}")
    print(f"sha256={p.get('sha256') or ''}")
    print(f"sha512={p.get('sha512') or ''}")
    print(f"verify_command={p.get('verify_command') or d.get('verify_command') or ''}")
    print(f"checksums_url={(d.get('checksums') or {}).get('sha256sums_url') or (d.get('checksums') or {}).get('sha512sums_url') or ''}")
    print(f"gpg_url={(d.get('checksums') or {}).get('sha256sums_gpg_url') or ''}")
    print(f"description={d.get('description') or ''}")
    extras = d.get("additional_isos") or []
    if extras:
        print("additional_isos:")
        for i, iso in enumerate(extras, 1):
            print(f"  [{i}] {iso.get('filename')}  {iso.get('label') or ''}")
elif q.startswith("iso:"):
    # iso:<distro>:<variant>
    _, distro, variant = q.split(":", 2)
    d = find_distro(distro)
    if not d:
        sys.exit(2)
    isos = [d.get("primary_iso") or {}] + list(d.get("additional_isos") or [])
    chosen = None
    if not variant or variant in ("", "0", "primary"):
        chosen = isos[0]
    else:
        # numeric 1-based into additional only? treat 0/1 as primary, 2+ additional
        if variant.isdigit():
            idx = int(variant)
            if idx <= 0 or idx > len(isos):
                sys.exit(3)
            chosen = isos[idx - 1]
        else:
            for iso in isos:
                if iso.get("filename") == variant:
                    chosen = iso
                    break
    if not chosen:
        sys.exit(3)
    # emit shell-eval friendly lines
    def esc(s):
        return (s or "").replace("'", "'\"'\"'")
    print(f"FILENAME='{esc(chosen.get('filename'))}'")
    print(f"URL='{esc(chosen.get('url'))}'")
    print(f"SHA256='{esc(chosen.get('sha256'))}'")
    print(f"SHA512='{esc(chosen.get('sha512'))}'")
    print(f"VERIFY='{esc(chosen.get('verify_command'))}'")
    print(f"LABEL='{esc(chosen.get('label'))}'")
else:
    sys.exit(1)
PY
}

# ---------------------------------------------------------------------------
# commands
# ---------------------------------------------------------------------------
cmd_list() {
  local meta
  meta=$(fetch_metadata "$API_URL" "$REFRESH")
  printf '%-22s %-18s %-14s %s\n' "ID" "NAME" "VERSION" "CODENAME"
  printf '%-22s %-18s %-14s %s\n' "--" "----" "-------" "--------"
  json_query "$meta" "list"
}

cmd_status() {
  local meta
  meta=$(fetch_metadata "$API_URL" "$REFRESH")
  json_query "$meta" "status"
}

cmd_info() {
  local distro="${1:-}"
  [[ -n "$distro" ]] || die "usage: osdl info <distro>"
  local meta
  meta=$(fetch_metadata "$API_URL" "$REFRESH")
  if ! json_query "$meta" "info:$distro"; then
    die "unknown distro: $distro (try: osdl list)"
  fi
  echo
  warn "GPG-verify official checksum files before trusting production media."
}

cmd_get() {
  local distro="${1:-}"
  [[ -n "$distro" ]] || die "usage: osdl get <distro> [--verify] [--print] [--output PATH] [--variant ID]"
  shift || true

  local do_verify=0 do_print=0 out="" variant="primary"
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --verify) do_verify=1; shift ;;
      --print)  do_print=1; shift ;;
      --output) out="${2:-}"; [[ -n "$out" ]] || die "--output requires a path"; shift 2 ;;
      --variant) variant="${2:-}"; [[ -n "$variant" ]] || die "--variant requires a value"; shift 2 ;;
      *) die "unknown option: $1" ;;
    esac
  done

  local meta evals
  meta=$(fetch_metadata "$API_URL" "$REFRESH")
  if ! evals=$(json_query "$meta" "iso:$distro:$variant"); then
    die "unknown distro/variant: $distro / $variant"
  fi
  # shellcheck disable=SC2086
  eval "$evals"
  [[ -n "${URL:-}" && -n "${FILENAME:-}" ]] || die "incomplete ISO metadata for $distro"

  local target="${out:-./$FILENAME}"

  if [[ "$do_print" -eq 1 ]]; then
    cat <<EOF
# Print-only mode — inspect before execution
# Distro: $distro
# File:   $FILENAME
# URL:    $URL

curl -fL --progress-bar -o $(printf '%q' "$target") $(printf '%q' "$URL")
EOF
    if [[ -n "${SHA256:-}" ]]; then
      printf 'echo "%s *%s" | sha256sum -c -\n' "$SHA256" "$(basename "$target")"
    elif [[ -n "${SHA512:-}" ]]; then
      printf 'echo "%s *%s" | sha512sum -c -\n' "$SHA512" "$(basename "$target")"
    fi
    echo
    echo "# WARNING: Still GPG-verify the official checksum file from the distribution project."
    return 0
  fi

  need_cmd curl
  info "downloading from official URL → $target"
  info "source: $URL"
  mkdir -p "$(dirname "$target")"
  curl -fL --progress-bar -A "$OSDL_USER_AGENT" -o "$target" "$URL"
  info "download complete: $target"

  if [[ "$do_verify" -eq 1 ]]; then
    cmd_verify_file "$target" "$distro" "$variant"
  else
    info "skipped verification (pass --verify). Always validate before use."
  fi
}

cmd_verify_file() {
  local file="$1" distro="$2" variant="${3:-primary}"
  [[ -f "$file" ]] || die "file not found: $file"
  local meta evals
  meta=$(fetch_metadata "$API_URL" "$REFRESH")
  if ! evals=$(json_query "$meta" "iso:$distro:$variant"); then
    die "unknown distro/variant: $distro / $variant"
  fi
  eval "$evals"

  local dir base
  dir=$(cd "$(dirname "$file")" && pwd)
  base=$(basename "$file")

  if [[ -n "${SHA256:-}" ]]; then
    need_cmd sha256sum
    info "verifying SHA256 for $base"
    # Hash is bound to official filename; allow local rename by hashing and comparing.
    local actual expected
    expected=$(printf '%s' "$SHA256" | tr '[:upper:]' '[:lower:]')
    actual=$(sha256sum "$file" | awk '{print $1}' | tr '[:upper:]' '[:lower:]')
    if [[ "$actual" == "$expected" ]]; then
      printf 'osdl: OK  %s  SHA256 match\n' "$file"
    else
      printf 'osdl: FAIL  %s\n  expected: %s\n  actual:   %s\n' "$file" "$expected" "$actual" >&2
      exit 1
    fi
  elif [[ -n "${SHA512:-}" ]]; then
    need_cmd sha512sum
    info "verifying SHA512 for $base"
    local actual expected
    expected=$(printf '%s' "$SHA512" | tr '[:upper:]' '[:lower:]')
    actual=$(sha512sum "$file" | awk '{print $1}' | tr '[:upper:]' '[:lower:]')
    if [[ "$actual" == "$expected" ]]; then
      printf 'osdl: OK  %s  SHA512 match\n' "$file"
    else
      printf 'osdl: FAIL  %s\n  expected: %s\n  actual:   %s\n' "$file" "$expected" "$actual" >&2
      exit 1
    fi
  else
    die "no hash published for this ISO variant"
  fi

  warn "Hash OK does not replace GPG verification of official checksum files."
}

cmd_verify() {
  local file="${1:-}" distro="${2:-}"
  [[ -n "$file" && -n "$distro" ]] || die "usage: osdl verify <file> <distro> [--variant ID]"
  shift 2 || true
  local variant="primary"
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --variant) variant="${2:-}"; shift 2 ;;
      *) die "unknown option: $1" ;;
    esac
  done
  cmd_verify_file "$file" "$distro" "$variant"
}

# ---------------------------------------------------------------------------
# main
# ---------------------------------------------------------------------------
API_URL="$OSDL_DEFAULT_API"
REFRESH=0
ARGS=()
while [[ $# -gt 0 ]]; do
  case "$1" in
    --api) API_URL="${2:-}"; [[ -n "$API_URL" ]] || die "--api requires a URL"; shift 2 ;;
    --refresh) REFRESH=1; shift ;;
    -h|--help) usage; exit 0 ;;
    *) ARGS+=("$1"); shift ;;
  esac
done
set -- "${ARGS[@]+"${ARGS[@]}"}"

CMD="${1:-help}"
shift || true

case "$CMD" in
  list)    cmd_list ;;
  info)    cmd_info "${1:-}" ;;
  get)     cmd_get "$@" ;;
  verify)  cmd_verify "$@" ;;
  status)  cmd_status ;;
  version|--version) printf 'osdl %s\n' "$OSDL_VERSION" ;;
  help|-h|--help) usage ;;
  *) die "unknown command: $CMD (try: osdl help)" ;;
esac
