#!/usr/bin/env bash
set -euo pipefail

BASE_URL="https://mysta.sh"
CREDENTIALS_FILE="$HOME/.mystash/credentials"
API_KEY="${MYSTASH_API_KEY:-}"
API_KEY_SOURCE="none"
if [[ -n "${MYSTASH_API_KEY:-}" ]]; then
  API_KEY_SOURCE="env"
fi
ALLOW_NON_DEFAULT_BASE_URL=0
SLUG=""
TITLE=""
DESCRIPTION=""
CLIENT=""
CLAIM_TOKEN=""
TARGET=""

usage() {
  cat <<'USAGE'
Usage: publish.sh <file-or-dir> [options]

Options:
  --api-key <key>         API key (or set $MYSTASH_API_KEY)
  --slug <slug>           Update existing publish
  --claim-token <token>   Claim token for updating anonymous publishes
  --title <text>          Viewer title
  --description <text>    Viewer description
  --client <name>         Agent name for attribution (e.g. hermes)
  --base-url <url>        API base (default: https://mysta.sh)
  --allow-non-default-base-url
                         Allow auth requests to non-default API base URL
USAGE
  exit 1
}

die() { echo "error: $1" >&2; exit 1; }

[[ -n "$(command -v jq 2>/dev/null)" ]] || die "requires jq"
for cmd in curl file; do
  command -v "$cmd" >/dev/null 2>&1 || die "requires $cmd"
done

while [[ $# -gt 0 ]]; do
  case "$1" in
    --api-key)      API_KEY="$2"; API_KEY_SOURCE="flag"; shift 2 ;;
    --slug)         SLUG="$2"; shift 2 ;;
    --claim-token)  CLAIM_TOKEN="$2"; shift 2 ;;
    --title)        TITLE="$2"; shift 2 ;;
    --description)  DESCRIPTION="$2"; shift 2 ;;
    --client)       CLIENT="$2"; shift 2 ;;
    --base-url)     BASE_URL="$2"; shift 2 ;;
    --allow-non-default-base-url) ALLOW_NON_DEFAULT_BASE_URL=1; shift ;;
    --help|-h)      usage ;;
    -*)             die "unknown option: $1" ;;
    *)              [[ -z "$TARGET" ]] && TARGET="$1" || die "unexpected argument: $1"; shift ;;
  esac
done

[[ -n "$TARGET" ]] || usage
[[ -e "$TARGET" ]] || die "path does not exist: $TARGET"

# Load API key from credentials file if not provided via flag or env
if [[ -z "$API_KEY" && -f "$CREDENTIALS_FILE" ]]; then
  API_KEY=$(cat "$CREDENTIALS_FILE" | tr -d '[:space:]')
  [[ -n "$API_KEY" ]] && API_KEY_SOURCE="credentials"
fi

BASE_URL="${BASE_URL%/}"
STATE_DIR=".mystash"
STATE_FILE="$STATE_DIR/state.json"

# Load claim token from state for anonymous updates if not provided explicitly.
if [[ -n "$SLUG" && -z "$CLAIM_TOKEN" && -f "$STATE_FILE" ]]; then
  CLAIM_TOKEN=$(jq -r --arg slug "$SLUG" '.publishes[$slug].claimToken // empty' "$STATE_FILE" | tr -d '[:space:]')
fi

# Safety guard: avoid accidentally sending bearer auth to arbitrary endpoints.
if [[ -n "$API_KEY" && "$BASE_URL" != "https://mysta.sh" && "$ALLOW_NON_DEFAULT_BASE_URL" -ne 1 ]]; then
  die "refusing to send API key to non-default base URL; pass --allow-non-default-base-url to override"
fi

compute_sha256() {
  local f="$1"
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$f" | cut -d' ' -f1
  else
    shasum -a 256 "$f" | cut -d' ' -f1
  fi
}

guess_content_type() {
  local f="$1"
  case "${f##*.}" in
    html|htm) echo "text/html; charset=utf-8" ;;
    css)      echo "text/css; charset=utf-8" ;;
    js|mjs)   echo "text/javascript; charset=utf-8" ;;
    json)     echo "application/json; charset=utf-8" ;;
    md|txt)   echo "text/plain; charset=utf-8" ;;
    svg)      echo "image/svg+xml" ;;
    png)      echo "image/png" ;;
    jpg|jpeg) echo "image/jpeg" ;;
    gif)      echo "image/gif" ;;
    webp)     echo "image/webp" ;;
    pdf)      echo "application/pdf" ;;
    mp4)      echo "video/mp4" ;;
    mp3)      echo "audio/mpeg" ;;
    ico)      echo "image/x-icon" ;;
    *)
      file --brief --mime-type "$f" 2>/dev/null || echo "application/octet-stream"
      ;;
  esac
}

# Build file manifest as JSON array
FILES_JSON="[]"
FILE_MAP="{}"

if [[ -f "$TARGET" ]]; then
  sz=$(wc -c < "$TARGET" | tr -d ' ')
  ct=$(guess_content_type "$TARGET")
  bn=$(basename "$TARGET")
  h=$(compute_sha256 "$TARGET")
  FILES_JSON=$(jq -n --arg p "$bn" --argjson s "$sz" --arg c "$ct" --arg h "$h" \
    '[{"path":$p,"size":$s,"contentType":$c,"hash":$h}]')
  FILE_MAP=$(jq -n --arg p "$bn" --arg a "$(cd "$(dirname "$TARGET")" && pwd)/$(basename "$TARGET")" \
    '{($p):$a}')
elif [[ -d "$TARGET" ]]; then
  while IFS= read -r -d '' f; do
    rel="${f#$TARGET/}"
    [[ "$rel" == ".DS_Store" ]] && continue
    [[ "$(basename "$rel")" == ".DS_Store" ]] && continue
    sz=$(wc -c < "$f" | tr -d ' ')
    ct=$(guess_content_type "$f")
    h=$(compute_sha256 "$f")
    abs=$(cd "$(dirname "$f")" && pwd)/$(basename "$f")
    FILES_JSON=$(echo "$FILES_JSON" | jq --arg p "$rel" --argjson s "$sz" --arg c "$ct" --arg h "$h" \
      '. + [{"path":$p,"size":$s,"contentType":$c,"hash":$h}]')
    FILE_MAP=$(echo "$FILE_MAP" | jq --arg p "$rel" --arg a "$abs" '. + {($p):$a}')
  done < <(find "$TARGET" -type f -print0 | sort -z)
else
  die "not a file or directory: $TARGET"
fi

file_count=$(echo "$FILES_JSON" | jq 'length')
[[ "$file_count" -gt 0 ]] || die "no files found"

# Build request body
BODY=$(echo "$FILES_JSON" | jq '{files: .}')

# Add viewer metadata if provided
if [[ -n "$TITLE" || -n "$DESCRIPTION" ]]; then
  viewer="{}"
  [[ -n "$TITLE" ]] && viewer=$(echo "$viewer" | jq --arg t "$TITLE" '.title = $t')
  [[ -n "$DESCRIPTION" ]] && viewer=$(echo "$viewer" | jq --arg d "$DESCRIPTION" '.description = $d')
  BODY=$(echo "$BODY" | jq --argjson v "$viewer" '.viewer = $v')
fi

# Determine endpoint and method
if [[ -n "$SLUG" ]]; then
  URL="$BASE_URL/api/v1/stashes/$SLUG"
  METHOD="PUT"
else
  URL="$BASE_URL/api/v1/publish"
  METHOD="POST"
fi

# Build auth header
AUTH_ARGS=()
if [[ -n "$API_KEY" ]]; then
  AUTH_ARGS=(-H "authorization: Bearer $API_KEY")
fi

CLAIM_ARGS=()
if [[ -n "$CLAIM_TOKEN" ]]; then
  CLAIM_ARGS=(-H "x-mystash-claim-token: $CLAIM_TOKEN")
fi

AUTH_MODE="anonymous"
[[ -n "$API_KEY" ]] && AUTH_MODE="authenticated"

# Client attribution
CLIENT_HEADER_VALUE="mystash/publish-sh"
if [[ -n "$CLIENT" ]]; then
  normalized_client=$(echo "$CLIENT" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9._-' '-')
  normalized_client="${normalized_client#-}"
  normalized_client="${normalized_client%-}"
  [[ -n "$normalized_client" ]] && CLIENT_HEADER_VALUE="${normalized_client}/publish-sh"
fi
CLIENT_ARGS=(-H "x-mystash-client: $CLIENT_HEADER_VALUE")

# Step 1: Create/update publish
echo "creating publish ($file_count files)..." >&2
RESPONSE=$(curl -sS -X "$METHOD" "$URL" \
  "${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"}" \
  "${CLAIM_ARGS[@]+"${CLAIM_ARGS[@]}"}" \
  "${CLIENT_ARGS[@]+"${CLIENT_ARGS[@]}"}" \
  -H "content-type: application/json" \
  -d "$BODY")

# Check for errors
if echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then
  err=$(echo "$RESPONSE" | jq -r '.error')
  details=$(echo "$RESPONSE" | jq -r '.details // empty')
  die "$err${details:+ ($details)}"
fi

OUT_SLUG=$(echo "$RESPONSE" | jq -r '.slug')
VERSION_ID=$(echo "$RESPONSE" | jq -r '.upload.versionId')
FINALIZE_URL=$(echo "$RESPONSE" | jq -r '.upload.finalizeUrl')
SITE_URL=$(echo "$RESPONSE" | jq -r '.siteUrl')
UPLOAD_COUNT=$(echo "$RESPONSE" | jq '.upload.uploads | length')
SKIPPED_COUNT=$(echo "$RESPONSE" | jq '.upload.skipped // [] | length')
RETURNED_CLAIM_TOKEN=$(echo "$RESPONSE" | jq -r '.claimToken // empty')
if [[ -z "$CLAIM_TOKEN" && -n "$RETURNED_CLAIM_TOKEN" && "$RETURNED_CLAIM_TOKEN" != "null" ]]; then
  CLAIM_TOKEN="$RETURNED_CLAIM_TOKEN"
  CLAIM_ARGS=(-H "x-mystash-claim-token: $CLAIM_TOKEN")
fi

[[ "$OUT_SLUG" != "null" ]] || die "unexpected response: $RESPONSE"

# Step 2: Upload files
if [[ "$SKIPPED_COUNT" -gt 0 ]]; then
  echo "uploading $UPLOAD_COUNT files ($SKIPPED_COUNT unchanged, skipped)..." >&2
else
  echo "uploading $UPLOAD_COUNT files..." >&2
fi
upload_errors=0

for i in $(seq 0 $((UPLOAD_COUNT - 1))); do
  upload_path=$(echo "$RESPONSE" | jq -r ".upload.uploads[$i].path")
  upload_url=$(echo "$RESPONSE" | jq -r ".upload.uploads[$i].url")
  upload_ct=$(echo "$RESPONSE" | jq -r ".upload.uploads[$i].headers[\"Content-Type\"] // empty")

  if [[ ! -d "$TARGET" ]]; then
    local_file="$TARGET"
  else
    local_file=$(echo "$FILE_MAP" | jq -r --arg p "$upload_path" '.[$p]')
  fi

  if [[ ! -f "$local_file" ]]; then
    echo "warning: missing local file for $upload_path" >&2
    upload_errors=$((upload_errors + 1))
    continue
  fi

  ct_args=()
  [[ -n "$upload_ct" ]] && ct_args=(-H "Content-Type: $upload_ct")

  http_code=$(curl -sS -o /dev/null -w "%{http_code}" -X PUT "$upload_url" \
    "${ct_args[@]+"${ct_args[@]}"}" \
    --data-binary "@$local_file")

  if [[ "$http_code" -lt 200 || "$http_code" -ge 300 ]]; then
    echo "warning: upload failed for $upload_path (HTTP $http_code)" >&2
    upload_errors=$((upload_errors + 1))
  fi
done

[[ "$upload_errors" -eq 0 ]] || die "$upload_errors file(s) failed to upload"

# Step 3: Finalize
echo "finalizing..." >&2
FIN_RESPONSE=$(curl -sS -X POST "$FINALIZE_URL" \
  "${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"}" \
  "${CLAIM_ARGS[@]+"${CLAIM_ARGS[@]}"}" \
  "${CLIENT_ARGS[@]+"${CLIENT_ARGS[@]}"}" \
  -H "content-type: application/json" \
  -d "{\"versionId\":\"$VERSION_ID\"}")

if echo "$FIN_RESPONSE" | jq -e '.error' >/dev/null 2>&1; then
  err=$(echo "$FIN_RESPONSE" | jq -r '.error')
  die "finalize failed: $err"
fi

# Save state
mkdir -p "$STATE_DIR"
if [[ -f "$STATE_FILE" ]]; then
  STATE=$(cat "$STATE_FILE")
else
  STATE='{"publishes":{}}'
fi

entry=$(jq -n --arg s "$SITE_URL" --arg t "$CLAIM_TOKEN" '{siteUrl: $s} + (if $t != "" then {claimToken: $t} else {} end)')

# Save to state
STATE=$(echo "$STATE" | jq --arg slug "$OUT_SLUG" --argjson e "$entry" '.publishes[$slug] = $e')
echo "$STATE" | jq '.' > "$STATE_FILE"

# Output
echo "$SITE_URL"

PERSISTENCE="permanent"
if [[ "$AUTH_MODE" == "anonymous" ]]; then
  PERSISTENCE="expires_24h"
fi

echo "" >&2
echo "publish_result.site_url=$SITE_URL" >&2
echo "publish_result.auth_mode=$AUTH_MODE" >&2
echo "publish_result.api_key_source=$API_KEY_SOURCE" >&2
echo "publish_result.persistence=$PERSISTENCE" >&2
if [[ -n "$CLAIM_TOKEN" ]]; then
  echo "publish_result.claim_token_saved=true" >&2
else
  echo "publish_result.claim_token_saved=false" >&2
fi

if [[ "$AUTH_MODE" == "authenticated" ]]; then
  echo "authenticated publish (permanent, saved to your account)" >&2
else
  echo "anonymous publish" >&2
fi
