#!/usr/bin/env bash
# hostrepo-b2put <bucket> <remote-dir> <file>...
# Upload files to B2 with the native API (b2_upload_file), each carrying:
#   X-Bz-Content-Sha1  the file's SHA-1. B2 checks it against the bytes it
#                      receives and rejects the upload if they differ, and
#                      reports it in every listing afterwards (the lite check).
#   X-Bz-Info-sha256   the file's SHA-256, stored with the file (fileInfo).
# The SHA-1 B2 echoes back is compared with the local one. One account
# authorization and one upload URL serve the whole batch.
#
# B2 access: B2_ACCOUNT_ID/B2_ACCOUNT_KEY or MIRROR_B2_ACCOUNT/MIRROR_B2_KEY.
# Exit: 0 all uploaded and confirmed, 2 otherwise.
set -euo pipefail
export LC_ALL=C
BUCKET="${1:?usage: hostrepo-b2put <bucket> <remote-dir> <file>...}"; DIR="${2:?remote dir}"; shift 2
ACCT="${B2_ACCOUNT_ID:-${MIRROR_B2_ACCOUNT:-}}"; KEY="${B2_ACCOUNT_KEY:-${MIRROR_B2_KEY:-}}"
[[ -n "$ACCT" && -n "$KEY" ]] || { echo "hostrepo-b2put: no B2 key" >&2; exit 2; }
C=(curl -sS -m 300 --retry 3 --retry-all-errors)
auth=$("${C[@]}" -u "$ACCT:$KEY" https://api.backblazeb2.com/b2api/v3/b2_authorize_account)
tok=$(jq -r '.authorizationToken // empty' <<<"$auth"); api=$(jq -r '.apiInfo.storageApi.apiUrl // empty' <<<"$auth")
[[ -n "$tok" ]] || { echo "hostrepo-b2put: authorization failed" >&2; exit 2; }
bid=$(jq -r --arg b "$BUCKET" 'if .apiInfo.storageApi.bucketName == $b then .apiInfo.storageApi.bucketId else empty end' <<<"$auth")
if [[ -z "$bid" ]]; then
  bid=$("${C[@]}" -H "Authorization: $tok" "$api/b2api/v3/b2_list_buckets?accountId=$(jq -r .accountId <<<"$auth")&bucketName=$BUCKET" | jq -r '.buckets[0].bucketId // empty')
fi
[[ -n "$bid" ]] || { echo "hostrepo-b2put: bucket $BUCKET not found" >&2; exit 2; }
up=""; utok=""
new_url() { local r; r=$("${C[@]}" -H "Authorization: $tok" "$api/b2api/v3/b2_get_upload_url?bucketId=$bid"); up=$(jq -r .uploadUrl <<<"$r"); utok=$(jq -r .authorizationToken <<<"$r"); }
new_url
urlenc() { jq -rn --arg s "$1" '$s | @uri | gsub("%2F"; "/")'; }
rc=0
for f in "$@"; do
  name="${DIR%/}/$(basename -- "$f")"; sha1=$(sha1sum < "$f" | cut -c1-40); sha256=$(sha256sum < "$f" | cut -c1-64)
  ok=0
  for _ in 1 2 3; do
    r=$(curl -sS -m 600 -X POST "$up" -H "Authorization: $utok" -H "X-Bz-File-Name: $(urlenc "$name")" \
          -H "Content-Type: b2/x-auto" -H "X-Bz-Content-Sha1: $sha1" -H "X-Bz-Info-sha256: $sha256" \
          -H "X-Bz-Info-src_last_modified_millis: $(( $(stat -c %Y "$f") * 1000 ))" --data-binary @"$f" 2>/dev/null || true)
    if [[ "$(jq -r '.contentSha1 // empty' <<<"$r")" == "$sha1" && "$(jq -r '.fileInfo.sha256 // empty' <<<"$r")" == "$sha256" ]]; then ok=1; break; fi
    new_url   # B2 asks clients to fetch a fresh upload URL after any failure
  done
  [[ $ok -eq 1 ]] || { echo "hostrepo-b2put: $name not confirmed: $(jq -r '.message // .code // "no response"' <<<"$r" 2>/dev/null)" >&2; rc=2; }
done
exit $rc
