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

usage() {
  echo "$0: bad arguments" >&2
  echo "Usage: $0 append <jsonl> <record-json>" >&2
  echo "       $0 set-status <jsonl> <task-id> <status>" >&2
  echo "       $0 set-priority <jsonl> <task-id> <priority>" >&2
  exit 2
}

check_jq() {
  if ! command -v jq >/dev/null 2>&1; then
    echo "$0: jq required but not found in PATH" >&2
    exit 3
  fi
}

timestamp_now() {
  date -u +"%Y-%m-%dT%H:%M:%SZ"
}

validate_record() {
  local record=$1
  check_jq

  if ! printf '%s' "$record" | jq -e . >/dev/null 2>&1; then
    echo "$0: append: record is not valid JSON" >&2
    return 1
  fi
  if [[ "$(printf '%s' "$record" | jq -r 'type')" != "object" ]]; then
    echo "$0: append: record must be a JSON object" >&2
    return 1
  fi

  local v task status ts
  v=$(printf '%s' "$record" | jq -r '.v // empty')
  task=$(printf '%s' "$record" | jq -r '.task // empty')
  status=$(printf '%s' "$record" | jq -r '.status // empty')
  ts=$(printf '%s' "$record" | jq -r '.ts // empty')

  if [[ "$v" != "backlog/v1" ]]; then
    echo "$0: append: record must have v='backlog/v1'" >&2
    return 1
  fi
  if [[ -z "$task" || -z "$status" || -z "$ts" ]]; then
    echo "$0: append: record missing required fields (v, task, status, ts)" >&2
    return 1
  fi
  return 0
}

with_lock() {
  local jsonl=$1
  local lockfile="${jsonl}.lock"
  mkdir -p "$(dirname "$jsonl")"
  exec {LOCK_FD}>"$lockfile"
  flock -x "$LOCK_FD" || {
    exec {LOCK_FD}>&-
    echo "$0: failed to acquire lock on $lockfile" >&2
    exit 3
  }
}

release_lock() {
  flock -u "$LOCK_FD"
  exec {LOCK_FD}>&-
}

append_record() {
  local jsonl=$1 record=$2
  with_lock "$jsonl"

  if ! validate_record "$record"; then
    release_lock
    exit 3
  fi

  local task compact
  task=$(printf '%s' "$record" | jq -r '.task')
  if [[ -f "$jsonl" ]] && jq -e --arg task "$task" 'select(.task == $task)' "$jsonl" >/dev/null 2>&1; then
    release_lock
    echo "$0: append: task already exists: $task" >&2
    exit 3
  fi

  compact=$(printf '%s' "$record" | jq -c .)
  if ! printf '%s\n' "$compact" >> "$jsonl"; then
    release_lock
    echo "$0: append: failed to write to $jsonl" >&2
    exit 3
  fi

  release_lock
}

set_status() {
  local jsonl=$1 task_id=$2 status=$3
  if [[ -z "$status" ]]; then
    echo "$0: set-status: status must be non-empty" >&2
    exit 3
  fi

  with_lock "$jsonl"
  check_jq

  if [[ ! -f "$jsonl" ]]; then
    release_lock
    echo "$0: set-status: backlog file not found: $jsonl" >&2
    exit 2
  fi

  local match_count
  if ! match_count=$(jq -r --arg task "$task_id" 'select(.task == $task) | .task' "$jsonl" 2>/dev/null | wc -l | tr -d ' '); then
    release_lock
    echo "$0: set-status: invalid JSON in backlog" >&2
    exit 3
  fi
  if [[ "$match_count" -eq 0 ]]; then
    release_lock
    echo "$0: set-status: task not found: $task_id" >&2
    exit 3
  fi
  if [[ "$match_count" -ne 1 ]]; then
    release_lock
    echo "$0: set-status: task must be unique: $task_id" >&2
    exit 3
  fi

  local tmp ts
  tmp=$(mktemp "${TMPDIR:-/tmp}/backlog.XXXXXX") || {
    release_lock
    echo "$0: set-status: failed to create temp file" >&2
    exit 3
  }
  ts=$(timestamp_now)

  if ! jq -c --arg task "$task_id" --arg status "$status" --arg ts "$ts" \
    'if .task == $task then .status = $status | .ts = $ts else . end' \
    "$jsonl" > "$tmp"; then
    rm -f "$tmp"
    release_lock
    echo "$0: set-status: failed to rewrite backlog" >&2
    exit 3
  fi

  if ! mv "$tmp" "$jsonl"; then
    rm -f "$tmp"
    release_lock
    echo "$0: set-status: failed to replace backlog" >&2
    exit 3
  fi

  release_lock
}

set_priority() {
  local jsonl=$1 task_id=$2 priority=$3
  if [[ "$priority" != "high" && "$priority" != "medium" && "$priority" != "low" ]]; then
    echo "$0: set-priority: priority must be one of high|medium|low" >&2
    exit 3
  fi

  with_lock "$jsonl"
  check_jq

  if [[ ! -f "$jsonl" ]]; then
    release_lock
    echo "$0: set-priority: backlog file not found: $jsonl" >&2
    exit 2
  fi

  local match_count
  if ! match_count=$(jq -r --arg task "$task_id" 'select((.task == $task) or (.backlogId == $task)) | .task' "$jsonl" 2>/dev/null | wc -l | tr -d ' '); then
    release_lock
    echo "$0: set-priority: invalid JSON in backlog" >&2
    exit 3
  fi
  if [[ "$match_count" -eq 0 ]]; then
    release_lock
    echo "$0: set-priority: task not found: $task_id" >&2
    exit 3
  fi
  if [[ "$match_count" -ne 1 ]]; then
    release_lock
    echo "$0: set-priority: task must be unique: $task_id" >&2
    exit 3
  fi

  local tmp ts
  tmp=$(mktemp "${TMPDIR:-/tmp}/backlog.XXXXXX") || {
    release_lock
    echo "$0: set-priority: failed to create temp file" >&2
    exit 3
  }
  ts=$(timestamp_now)

  if ! jq -c --arg task "$task_id" --arg priority "$priority" --arg ts "$ts" \
    'if (.task == $task) or (.backlogId == $task) then .priority = $priority | .ts = $ts else . end' \
    "$jsonl" > "$tmp"; then
    rm -f "$tmp"
    release_lock
    echo "$0: set-priority: failed to rewrite backlog" >&2
    exit 3
  fi

  if ! mv "$tmp" "$jsonl"; then
    rm -f "$tmp"
    release_lock
    echo "$0: set-priority: failed to replace backlog" >&2
    exit 3
  fi

  release_lock
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  cmd=${1:-}
  shift || true
  case "$cmd" in
    append)
      [[ $# -eq 2 ]] || usage
      append_record "$1" "$2"
      ;;
    set-status)
      [[ $# -eq 3 ]] || usage
      set_status "$1" "$2" "$3"
      ;;
    set-priority)
      [[ $# -eq 3 ]] || usage
      set_priority "$1" "$2" "$3"
      ;;
    *)
      usage
      ;;
  esac
fi
