#!/usr/bin/env ysh

# SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
#
# SPDX-License-Identifier: CC0-1.0

const collection = 'sh.tangled.repo.artifact'
const maximum_blob_size = 50 * 1024 * 1024
const maximum_scan_limit = 10000
const targets = [
  {os: 'linux', arch: 'amd64', extension: '', static: true},
  {os: 'linux', arch: 'arm64', extension: '', static: true},
  {os: 'darwin', arch: 'amd64', extension: '', static: false},
  {os: 'darwin', arch: 'arm64', extension: '', static: false},
  {os: 'windows', arch: 'amd64', extension: '.exe', static: false},
  {os: 'freebsd', arch: 'amd64', extension: '', static: true},
]

proc build_artifacts (release_tag, output_dir) {
  if (release_tag !~ '^v.+$') {
    echo "Release tag must start with v: $release_tag" >&2
    exit 1
  }
  if (output_dir === '') {
    echo 'Release output directory must not be empty' >&2
    exit 1
  }
  var go_version = $(go env GOVERSION)
  if (go_version !== 'go1.27.0') {
    echo "Release requires Go 1.27.0, found $go_version" >&2
    exit 1
  }

  install -d -m 0755 "$output_dir"
  var existing_path = $(find "$output_dir" -mindepth 1 -maxdepth 1 -print -quit)
  if (existing_path !== '') {
    echo "Release output directory is not empty: $output_dir" >&2
    exit 1
  }

  for target in (targets) {
    var name = "cooked-mcp-$release_tag-$[target.os]-$[target.arch]$[target.extension]"
    var artifact = "$output_dir/$name"
    var ldflags = "-s -w -buildid= -X main.version=$release_tag"
    if (target.static) {
      setvar ldflags = "-d $ldflags -extldflags=-static"
    }

    echo "Building $[target.os]/$[target.arch] ..."
    env \
      CGO_ENABLED=0 \
      GOAMD64=v1 \
      GOOS="$[target.os]" \
      GOARCH="$[target.arch]" \
      GOTOOLCHAIN=local \
      go build \
        -trimpath \
        -tags 'netgo,osusergo,static_build' \
        -ldflags "$ldflags" \
        -o "$artifact" \
        .

    if ! test -s "$artifact" {
      echo "Expected artifact is missing or empty: $artifact" >&2
      exit 1
    }
    var size = int($(wc -c < "$artifact"))
    if (size > maximum_blob_size) {
      echo "Artifact exceeds the 50 MiB lexicon limit: $artifact" >&2
      exit 1
    }
  }
}

var build_only = false
var tag = ''
var staging = ''
if (len(ARGV) === 0) {
  setvar tag = get(ENV, 'TANGLED_REF_NAME', '')
} else {
  if (len(ARGV) !== 3 or ARGV[0] !== '--build-only') {
    echo "Usage: $0 [--build-only TAG OUTPUT_DIR]" >&2
    exit 2
  }
  setvar build_only = true
  setvar tag = ARGV[1]
  setvar staging = ARGV[2]
}

# distribution.VERIFICATION.2
if (build_only) {
  build_artifacts "$tag" "$staging"
  echo "Built $[len(targets)] release artifacts for $tag"
  exit 0
}

const pds = get(ENV, 'COOKED_MCP_RELEASE_PDS', 'https://sequeste.red')
const identifier = get(ENV, 'COOKED_MCP_RELEASE_IDENTIFIER', 'secluded.site')
const password_file = get(ENV, 'COOKED_MCP_RELEASE_APP_PASSWORD_FILE', '')
const scan_limit_text = get(ENV, 'COOKED_MCP_RELEASE_RECORD_SCAN_LIMIT', '100')

if (password_file === '') {
  echo 'COOKED_MCP_RELEASE_APP_PASSWORD_FILE is required' >&2
  exit 1
}
if ! test -s "$password_file" {
  echo "App password file is missing or empty: $password_file" >&2
  exit 1
}
if (scan_limit_text !~ '^[0-9]+$') {
  echo 'COOKED_MCP_RELEASE_RECORD_SCAN_LIMIT must be a positive integer' >&2
  exit 1
}
const scan_limit = int(scan_limit_text)
if (scan_limit < 1 or scan_limit > maximum_scan_limit) {
  echo "COOKED_MCP_RELEASE_RECORD_SCAN_LIMIT must be between 1 and $maximum_scan_limit" >&2
  exit 1
}
if (pds !~ '^https://') {
  echo 'COOKED_MCP_RELEASE_PDS must use HTTPS' >&2
  exit 1
}
if (pds ~ '/$') {
  echo 'COOKED_MCP_RELEASE_PDS must not end with a slash' >&2
  exit 1
}

const required_environment = [
  'TANGLED_PIPELINE_KIND',
  'TANGLED_REF',
  'TANGLED_REF_NAME',
  'TANGLED_REF_TYPE',
  'TANGLED_SHA',
  'TANGLED_REPO_DID',
  'TANGLED_REPO_REPO_DID',
]
for name in (required_environment) {
  if (get(ENV, name, '') === '') {
    echo "$name is required" >&2
    exit 1
  }
}

# distribution.RELEASE.1 distribution.RELEASE.2
if (ENV.TANGLED_PIPELINE_KIND !== 'push') {
  echo 'Releases require a push pipeline' >&2
  exit 1
}
if (ENV.TANGLED_REF_TYPE !== 'tag') {
  echo 'Releases require a tag ref' >&2
  exit 1
}
if (ENV.TANGLED_REF !== "refs/tags/$tag") {
  echo "TANGLED_REF does not match TANGLED_REF_NAME: $[ENV.TANGLED_REF]" >&2
  exit 1
}
if (tag !~ '^v.+$') {
  echo "Release tag must start with v: $tag" >&2
  exit 1
}

const work = $(mktemp -d)
trap --add EXIT {
  rm -rf -- "$work"
}
chmod 0700 "$work"
setvar staging = "$work/dist"

const tag_object = $(git rev-parse --verify "$tag^{tag}")
if (tag_object !== ENV.TANGLED_SHA) {
  echo 'The pushed tag object does not match TANGLED_SHA' >&2
  exit 1
}
const release_commit = $(git rev-parse --verify "$tag^{}")
const checkout_commit = $(git rev-parse --verify HEAD)
if (release_commit !== checkout_commit) {
  echo 'The annotated tag does not resolve to the checked-out commit' >&2
  exit 1
}
const tag_bytes = $(printf '%s' "$tag_object" | xxd -r -p | base64 -w 0 | tr -d '=')
if (len(tag_bytes) !== 27) {
  echo 'The annotated tag object ID is not a 20-byte SHA-1 value' >&2
  exit 1
}

const auth_config = "$work/curl-auth.conf"
const password = $(cat -- "$password_file")
var session_request = {
  identifier: identifier,
  password: password,
}
json write (session_request) > "$work/session-request.json"
chmod 0600 "$work/session-request.json"

const curl_arguments = [
  '--silent',
  '--show-error',
  '--fail-with-body',
  '--connect-timeout', '10',
  '--max-time', '120',
]

echo 'Authenticating to the release PDS ...'
curl @curl_arguments \
  --request POST \
  --header 'Content-Type: application/json' \
  --data-binary "@$work/session-request.json" \
  "$pds/xrpc/com.atproto.server.createSession" \
  --output "$work/session.json"
var session = null
try {
  json read (&session) < "$work/session.json" 2>/dev/null
}
if (_status !== 0) {
  echo 'PDS returned an invalid session response' >&2
  exit 1
}
const publisher_did = session.did
const access_token = session.accessJwt
if (publisher_did !== ENV.TANGLED_REPO_DID) {
  echo 'Authenticated PDS account does not match TANGLED_REPO_DID' >&2
  exit 1
}
printf 'header = "Authorization: Bearer %s"\n' "$access_token" > "$auth_config"
chmod 0600 "$auth_config"

proc scan_release (output_path) {
  var matches = []
  var cursor = ''
  var scanned = 0
  while (scanned < scan_limit) {
    var page_size = scan_limit - scanned
    if (page_size > 100) {
      setvar page_size = 100
    }
    var cursor_arguments = []
    if (cursor !== '') {
      setvar cursor_arguments = ['--data-urlencode', "cursor=$cursor"]
    }
    curl @curl_arguments \
      --retry 2 \
      --retry-all-errors \
      --get \
      --data-urlencode "repo=$publisher_did" \
      --data-urlencode "collection=$collection" \
      --data-urlencode "limit=$page_size" \
      @cursor_arguments \
      "$pds/xrpc/com.atproto.repo.listRecords" \
      --output "$work/records-page.json"

    var page = null
    json read (&page) < "$work/records-page.json"
    for record in (page.records) {
      setvar scanned += 1
      var value = record.value
      var record_tag = get(value, 'tag', {})
      var record_tag_bytes = get(record_tag, '$bytes', '')
      if (
        get(value, 'repoDid', '') === ENV.TANGLED_REPO_REPO_DID and
        (record_tag_bytes === tag_bytes or record_tag_bytes === "$tag_bytes=")
      ) {
        call matches->append(record)
      }
    }
    if (len(page.records) === 0 or scanned >= scan_limit) {
      break
    }
    setvar cursor = get(page, 'cursor', '')
    if (cursor === '') {
      break
    }
  }
  json write (matches) > "$output_path"
}

# distribution.RELEASE.4
echo "Checking the newest $scan_limit artifact records for $tag ..."
scan_release "$work/preflight-records.json"
var preflight_records = null
json read (&preflight_records) < "$work/preflight-records.json"
if (len(preflight_records) !== 0) {
  echo "This release already has published artifacts in the newest $scan_limit records" >&2
  echo 'Create and push a new tag instead of overwriting a release' >&2
  exit 1
}

# distribution.RELEASE.3
build_artifacts "$tag" "$staging"

var records = []
const created_at = $(date --utc '+%Y-%m-%dT%H:%M:%SZ')
for target in (targets) {
  const name = "cooked-mcp-$tag-$[target.os]-$[target.arch]$[target.extension]"
  const artifact = "$staging/$name"
  const size = int($(wc -c < "$artifact"))
  echo "Uploading $name ..."
  curl @curl_arguments \
    --config "$auth_config" \
    --request POST \
    --header 'Content-Type: application/octet-stream' \
    --data-binary "@$artifact" \
    "$pds/xrpc/com.atproto.repo.uploadBlob" \
    --output "$work/blob.json"
  var upload = null
  json read (&upload) < "$work/blob.json"
  if (upload.blob.size !== size) {
    echo "PDS returned invalid blob metadata for $name" >&2
    exit 1
  }
  call records->append({
    '$type': collection,
    name: name,
    repoDid: ENV.TANGLED_REPO_REPO_DID,
    tag: {'$bytes': tag_bytes},
    createdAt: created_at,
    artifact: upload.blob,
  })
}

curl @curl_arguments \
  --retry 2 \
  --retry-all-errors \
  --get \
  --data-urlencode "did=$publisher_did" \
  "$pds/xrpc/com.atproto.sync.getLatestCommit" \
  --output "$work/latest-commit.json"
var latest_commit = null
json read (&latest_commit) < "$work/latest-commit.json"

echo 'Rechecking publication state before creating records ...'
scan_release "$work/prewrite-records.json"
var prewrite_records = null
json read (&prewrite_records) < "$work/prewrite-records.json"
if (len(prewrite_records) !== 0) {
  echo 'Another pipeline published this release while artifacts were being prepared' >&2
  echo 'Create and push a new tag instead of overwriting a release' >&2
  exit 1
}

var writes = []
for record in (records) {
  call writes->append({
    '$type': 'com.atproto.repo.applyWrites#create',
    collection: collection,
    value: record,
  })
}
var apply_request = {
  repo: publisher_did,
  swapCommit: latest_commit.cid,
  writes: writes,
}
json write (apply_request) > "$work/apply-request.json"

echo 'Publishing artifact records ...'
var http_status = ''
try {
  setvar http_status = $(curl @curl_arguments \
    --config "$auth_config" \
    --request POST \
    --header 'Content-Type: application/json' \
    --data-binary "@$work/apply-request.json" \
    --output "$work/apply-response.json" \
    --write-out '%{http_code}' \
    "$pds/xrpc/com.atproto.repo.applyWrites")
}
const apply_curl_status = _status

echo 'Confirming published artifact records ...'
scan_release "$work/published-records.json"
var published_records = null
json read (&published_records) < "$work/published-records.json"
var exact_publication = len(published_records) === len(records)
for expected in (records) {
  var match_count = 0
  for published in (published_records) {
    var value = published.value
    var value_tag = get(value, 'tag', {})
    var value_tag_bytes = get(value_tag, '$bytes', '')
    if (value_tag_bytes === "$tag_bytes=") {
      setvar value.tag = {'$bytes': tag_bytes}
    }
    if (value === expected) {
      setvar match_count += 1
    }
  }
  if (match_count !== 1) {
    setvar exact_publication = false
  }
}

if (exact_publication) {
  echo "Published $tag with $[len(records)] artifacts"
  exit 0
}
if (len(published_records) !== 0) {
  echo 'Publication left an ambiguous or partial release state' >&2
  echo 'Inspect the PDS records and create a new tag before retrying' >&2
  exit 1
}
if (apply_curl_status !== 0 or http_status !~ '^2[0-9][0-9]$') {
  echo "PDS did not publish the release (curl status $apply_curl_status, HTTP $http_status)" >&2
  echo 'No matching artifact records were found; this tag may be retried after diagnosing the failure' >&2
  exit 1
}

echo 'PDS accepted applyWrites but the expected records were not found' >&2
exit 1
