#!/usr/bin/env sh
set -eu

# Deterministic Jira Cloud CLI wrapper.
# Supports:
#   - OAuth 2.0 (3LO): Bearer token via api.atlassian.com/ex/jira/{cloudid}
#   - Basic auth: email + API token via https://<site>.atlassian.net/rest/api/3
# Dependencies: curl, sed, awk, mktemp, base64
# Optional dependency for OAuth cloudid auto-discovery: node

API_HOST="https://api.atlassian.com"
AUTH_HOST="https://auth.atlassian.com"
MAX_RETRIES="${JIRA_MAX_RETRIES:-3}"
RETRY_BASE_SEC="${JIRA_RETRY_BASE_SEC:-2}"
JIRA_AUTH_MODE="${JIRA_AUTH_MODE:-oauth}"
JIRA_ALLOWED_SITE_HOST="${JIRA_ALLOWED_SITE_HOST:-tidb.atlassian.net}"

SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
AUDIT_LOG_DIR="${AUDIT_LOG_DIR:-${SKILL_DIR}/audit-logs}"
ENV_FILE="${JIRA_ENV_FILE:-${SKILL_DIR}/.env}"
if [ -f "$ENV_FILE" ]; then
  set -a
  # shellcheck source=/dev/null
  . "$ENV_FILE"
  set +a
fi

usage() {
  cat <<'EOF'
jira_api.sh - Jira Cloud API helper

Environment:
  JIRA_AUTH_MODE        oauth|basic (default: oauth)

  OAuth mode:
    JIRA_TOKEN          OAuth access token
    JIRA_SITE_URL       site URL for cloudid auto-discovery (recommended)
    JIRA_CLOUD_ID       cloudid (optional if JIRA_SITE_URL is set)

  Basic mode:
    JIRA_TOKEN          Atlassian API token
    JIRA_EMAIL          Atlassian account email
    JIRA_SITE_URL       site URL (e.g. https://tidb.atlassian.net)

  Security & audit:
    JIRA_ALLOWED_SITE_HOST allowed site host (default: tidb.atlassian.net)
    AUDIT_LOG_DIR       audit log directory (default: ${SKILL_DIR}/audit-logs)
    JIRA_AUDIT_CONTEXT  optional context/reason recorded in write audit logs

  Retry:
    JIRA_MAX_RETRIES    retry attempts for 429/5xx (default: 3)
    JIRA_RETRY_BASE_SEC base backoff seconds (default: 2)

Commands:
  auth-url --client-id ID --redirect-uri URI --scopes SCOPES --state STATE
  token-exchange --client-id ID --client-secret SECRET --code CODE --redirect-uri URI
  accessible-resources              (OAuth mode only)

  project-search --query TEXT [--max-results N] [--start-at N] [--order-by FIELD]
  project-get --project KEY_OR_ID

  issue-create --payload-file FILE
  issue-get --issue KEY_OR_ID
  issue-update --issue KEY_OR_ID --payload-file FILE
  issue-delete --issue KEY_OR_ID [--delete-subtasks] --confirm-delete KEY_OR_ID --human-confirm YES
  issue-search-jql --payload-file FILE

Notes:
  - Payload files must be valid JSON.
  - issue-delete requires explicit --confirm-delete to reduce accidental deletions.
EOF
}

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || {
    echo "ERROR: required command not found: $1" >&2
    exit 127
  }
}

require_env() {
  var_name="$1"
  eval "var_value=\${$var_name:-}"
  [ -n "$var_value" ] || {
    echo "ERROR: missing environment variable: $var_name" >&2
    exit 2
  }
}

urlencode() {
  printf '%s' "$1" | sed \
    -e 's/%/%25/g' \
    -e 's/ /%20/g' \
    -e 's/"/%22/g' \
    -e "s/'/%27/g" \
    -e 's/#/%23/g' \
    -e 's/&/%26/g' \
    -e 's/+/%2B/g' \
    -e 's/,/%2C/g' \
    -e 's/:/%3A/g' \
    -e 's/;/%3B/g' \
    -e 's/?/%3F/g' \
    -e 's/@/%40/g' \
    -e 's/=/%3D/g' \
    -e 's/\//%2F/g'
}

normalized_site_url() {
  require_env JIRA_SITE_URL
  printf '%s' "$JIRA_SITE_URL" | sed 's#/*$##'
}

site_host() {
  site_url="$(normalized_site_url)"
  printf '%s' "$site_url" | sed -E 's#^https://([^/]+).*$#\1#'
}

assert_site_security() {
  site_url="$(normalized_site_url)"
  case "$site_url" in
    https://*) ;;
    *) echo "ERROR: JIRA_SITE_URL must use https://" >&2; exit 2 ;;
  esac

  host="$(site_host)"
  [ "$host" = "$JIRA_ALLOWED_SITE_HOST" ] || {
    echo "ERROR: JIRA_SITE_URL host '$host' not allowed (expected: $JIRA_ALLOWED_SITE_HOST)" >&2
    exit 2
  }
}

ensure_audit_dir() {
  mkdir -p "$AUDIT_LOG_DIR"
}

audit_write() {
  action="$1"
  target="$2"
  ensure_audit_dir
  day="$(date -u +%F)"
  ts="$(date -u +%FT%TZ)"
  host="unknown"
  if [ -n "${JIRA_SITE_URL:-}" ]; then
    host="$(site_host)"
  fi
  context="${JIRA_AUDIT_CONTEXT:-}"
  printf '%s\taction=%s\ttarget=%s\thost=%s\tauth=%s\tcontext=%s\n' \
    "$ts" "$action" "$target" "$host" "$JIRA_AUTH_MODE" "$context" >> "$AUDIT_LOG_DIR/$day.log"
}

resolve_cloud_id() {
  if [ -n "${JIRA_CLOUD_ID:-}" ]; then
    printf '%s' "$JIRA_CLOUD_ID"
    return 0
  fi

  [ "$JIRA_AUTH_MODE" = "oauth" ] || {
    echo "ERROR: JIRA_CLOUD_ID auto-discovery is OAuth-only" >&2
    exit 2
  }

  require_env JIRA_SITE_URL
  require_env JIRA_TOKEN
  require_cmd node

  resources_json="$(http_request GET "$API_HOST/oauth/token/accessible-resources")"
  cloud_id="$(printf '%s' "$resources_json" | JIRA_SITE_URL="$JIRA_SITE_URL" node -e '
    const fs = require("fs");
    const input = fs.readFileSync(0, "utf8");
    const target = String(process.env.JIRA_SITE_URL || "").replace(/\/+$/, "");
    let data;
    try { data = JSON.parse(input); } catch { process.exit(2); }
    const hit = Array.isArray(data)
      ? data.find((x) => String((x && x.url) || "").replace(/\/+$/, "") === target)
      : null;
    if (!hit || !hit.id) process.exit(2);
    process.stdout.write(String(hit.id));
  ' 2>/dev/null)"

  [ -n "$cloud_id" ] || {
    echo "ERROR: cannot resolve cloudid for JIRA_SITE_URL=$JIRA_SITE_URL" >&2
    exit 2
  }

  export JIRA_CLOUD_ID="$cloud_id"
  echo "INFO: resolved JIRA_CLOUD_ID=$cloud_id from accessible-resources" >&2
  printf '%s' "$cloud_id"
}

jira_base() {
  case "$JIRA_AUTH_MODE" in
    oauth)
      cloud_id="$(resolve_cloud_id)"
      printf '%s/ex/jira/%s/rest/api/3' "$API_HOST" "$cloud_id"
      ;;
    basic)
      site_url="$(normalized_site_url)"
      printf '%s/rest/api/3' "$site_url"
      ;;
    *)
      echo "ERROR: unsupported JIRA_AUTH_MODE=$JIRA_AUTH_MODE (expected oauth|basic)" >&2
      exit 2
      ;;
  esac
}

auth_header() {
  case "$JIRA_AUTH_MODE" in
    oauth)
      require_env JIRA_TOKEN
      printf 'Authorization: Bearer %s' "$JIRA_TOKEN"
      ;;
    basic)
      require_env JIRA_TOKEN
      require_env JIRA_EMAIL
      encoded="$(printf '%s:%s' "$JIRA_EMAIL" "$JIRA_TOKEN" | base64 | tr -d '\n')"
      printf 'Authorization: Basic %s' "$encoded"
      ;;
    *)
      echo "ERROR: unsupported JIRA_AUTH_MODE=$JIRA_AUTH_MODE (expected oauth|basic)" >&2
      exit 2
      ;;
  esac
}

http_request() {
  method="$1"
  url="$2"
  data_file="${3:-}"
  content_type="${4:-application/json}"

  attempt=1
  while [ "$attempt" -le "$MAX_RETRIES" ]; do
    headers_file="$(mktemp)"
    body_file="$(mktemp)"
    ah="$(auth_header)"

    if [ -n "$data_file" ]; then
      status="$(curl -sS -X "$method" "$url" \
        -H "$ah" \
        -H "Accept: application/json" \
        -H "Content-Type: $content_type" \
        --data @"$data_file" \
        -D "$headers_file" -o "$body_file" -w "%{http_code}")"
    else
      status="$(curl -sS -X "$method" "$url" \
        -H "$ah" \
        -H "Accept: application/json" \
        -D "$headers_file" -o "$body_file" -w "%{http_code}")"
    fi

    case "$status" in
      2??)
        cat "$body_file"
        rm -f "$headers_file" "$body_file"
        return 0
        ;;
      429|5??)
        retry_after="$(awk 'BEGIN{IGNORECASE=1} /^Retry-After:/ {gsub("\r", "", $2); print $2}' "$headers_file" | tail -n 1)"
        rm -f "$headers_file" "$body_file"

        if [ "$attempt" -ge "$MAX_RETRIES" ]; then
          echo "ERROR: HTTP $status after $attempt attempts: $url" >&2
          return 1
        fi

        if [ -n "$retry_after" ]; then
          sleep_sec="$retry_after"
        else
          sleep_sec=$((RETRY_BASE_SEC * attempt))
        fi

        echo "WARN: HTTP $status, retrying in ${sleep_sec}s (attempt $attempt/$MAX_RETRIES)" >&2
        sleep "$sleep_sec"
        attempt=$((attempt + 1))
        ;;
      *)
        echo "ERROR: HTTP $status: $url" >&2
        cat "$body_file" >&2
        rm -f "$headers_file" "$body_file"
        return 1
        ;;
    esac
  done

  return 1
}

require_file() {
  file="$1"
  [ -f "$file" ] || {
    echo "ERROR: file not found: $file" >&2
    exit 2
  }
}

cmd="${1:-}"
[ -n "$cmd" ] || {
  usage
  exit 1
}
shift || true

require_cmd curl
require_cmd sed
require_cmd awk
require_cmd mktemp
require_cmd base64
require_cmd date

case "$cmd" in
  project-search|project-get|issue-create|issue-get|issue-update|issue-delete|issue-search-jql)
    assert_site_security
    ;;
esac

case "$cmd" in
  auth-url)
    client_id=""
    redirect_uri=""
    scopes=""
    state=""
    while [ "$#" -gt 0 ]; do
      case "$1" in
        --client-id) client_id="$2"; shift 2 ;;
        --redirect-uri) redirect_uri="$2"; shift 2 ;;
        --scopes) scopes="$2"; shift 2 ;;
        --state) state="$2"; shift 2 ;;
        *) echo "ERROR: unknown option: $1" >&2; exit 2 ;;
      esac
    done

    [ -n "$client_id" ] || { echo "ERROR: --client-id required" >&2; exit 2; }
    [ -n "$redirect_uri" ] || { echo "ERROR: --redirect-uri required" >&2; exit 2; }
    [ -n "$scopes" ] || { echo "ERROR: --scopes required" >&2; exit 2; }
    [ -n "$state" ] || { echo "ERROR: --state required" >&2; exit 2; }

    echo "${AUTH_HOST}/authorize?audience=api.atlassian.com&client_id=$(urlencode "$client_id")&scope=$(urlencode "$scopes")&redirect_uri=$(urlencode "$redirect_uri")&state=$(urlencode "$state")&response_type=code&prompt=consent"
    ;;

  token-exchange)
    client_id=""
    client_secret=""
    code=""
    redirect_uri=""

    while [ "$#" -gt 0 ]; do
      case "$1" in
        --client-id) client_id="$2"; shift 2 ;;
        --client-secret) client_secret="$2"; shift 2 ;;
        --code) code="$2"; shift 2 ;;
        --redirect-uri) redirect_uri="$2"; shift 2 ;;
        *) echo "ERROR: unknown option: $1" >&2; exit 2 ;;
      esac
    done

    [ -n "$client_id" ] || { echo "ERROR: --client-id required" >&2; exit 2; }
    [ -n "$client_secret" ] || { echo "ERROR: --client-secret required" >&2; exit 2; }
    [ -n "$code" ] || { echo "ERROR: --code required" >&2; exit 2; }
    [ -n "$redirect_uri" ] || { echo "ERROR: --redirect-uri required" >&2; exit 2; }

    payload_file="$(mktemp)"
    cat >"$payload_file" <<EOF
{"grant_type":"authorization_code","client_id":"$client_id","client_secret":"$client_secret","code":"$code","redirect_uri":"$redirect_uri"}
EOF

    body_file="$(mktemp)"
    status="$(curl -sS -X POST "$AUTH_HOST/oauth/token" \
      -H "Accept: application/json" \
      -H "Content-Type: application/json" \
      --data @"$payload_file" \
      -o "$body_file" -w "%{http_code}")"

    rm -f "$payload_file"

    case "$status" in
      2??) cat "$body_file"; rm -f "$body_file" ;;
      *) echo "ERROR: HTTP $status: $AUTH_HOST/oauth/token" >&2; cat "$body_file" >&2; rm -f "$body_file"; exit 1 ;;
    esac
    ;;

  accessible-resources)
    [ "$JIRA_AUTH_MODE" = "oauth" ] || { echo "ERROR: accessible-resources is OAuth-only" >&2; exit 2; }
    require_env JIRA_TOKEN
    http_request GET "$API_HOST/oauth/token/accessible-resources"
    ;;

  project-search)
    query=""
    max_results="50"
    start_at="0"
    order_by=""
    while [ "$#" -gt 0 ]; do
      case "$1" in
        --query) query="$2"; shift 2 ;;
        --max-results) max_results="$2"; shift 2 ;;
        --start-at) start_at="$2"; shift 2 ;;
        --order-by) order_by="$2"; shift 2 ;;
        *) echo "ERROR: unknown option: $1" >&2; exit 2 ;;
      esac
    done
    [ -n "$query" ] || { echo "ERROR: --query required" >&2; exit 2; }
    q="query=$(urlencode "$query")&maxResults=$(urlencode "$max_results")&startAt=$(urlencode "$start_at")"
    if [ -n "$order_by" ]; then
      q="$q&orderBy=$(urlencode "$order_by")"
    fi
    http_request GET "$(jira_base)/project/search?$q"
    ;;

  project-get)
    project=""
    while [ "$#" -gt 0 ]; do
      case "$1" in
        --project) project="$2"; shift 2 ;;
        *) echo "ERROR: unknown option: $1" >&2; exit 2 ;;
      esac
    done
    [ -n "$project" ] || { echo "ERROR: --project required" >&2; exit 2; }
    http_request GET "$(jira_base)/project/$(urlencode "$project")"
    ;;

  issue-create)
    payload_file=""
    while [ "$#" -gt 0 ]; do
      case "$1" in
        --payload-file) payload_file="$2"; shift 2 ;;
        *) echo "ERROR: unknown option: $1" >&2; exit 2 ;;
      esac
    done
    [ -n "$payload_file" ] || { echo "ERROR: --payload-file required" >&2; exit 2; }
    require_file "$payload_file"
    audit_write "issue-create" "new-issue"
    http_request POST "$(jira_base)/issue" "$payload_file"
    ;;

  issue-get)
    issue=""
    while [ "$#" -gt 0 ]; do
      case "$1" in
        --issue) issue="$2"; shift 2 ;;
        *) echo "ERROR: unknown option: $1" >&2; exit 2 ;;
      esac
    done
    [ -n "$issue" ] || { echo "ERROR: --issue required" >&2; exit 2; }
    http_request GET "$(jira_base)/issue/$(urlencode "$issue")"
    ;;

  issue-update)
    issue=""
    payload_file=""
    while [ "$#" -gt 0 ]; do
      case "$1" in
        --issue) issue="$2"; shift 2 ;;
        --payload-file) payload_file="$2"; shift 2 ;;
        *) echo "ERROR: unknown option: $1" >&2; exit 2 ;;
      esac
    done
    [ -n "$issue" ] || { echo "ERROR: --issue required" >&2; exit 2; }
    [ -n "$payload_file" ] || { echo "ERROR: --payload-file required" >&2; exit 2; }
    require_file "$payload_file"
    audit_write "issue-update" "$issue"
    http_request PUT "$(jira_base)/issue/$(urlencode "$issue")" "$payload_file"
    ;;

  issue-delete)
    issue=""
    confirm=""
    human_confirm=""
    delete_subtasks="false"
    while [ "$#" -gt 0 ]; do
      case "$1" in
        --issue) issue="$2"; shift 2 ;;
        --confirm-delete) confirm="$2"; shift 2 ;;
        --human-confirm) human_confirm="$2"; shift 2 ;;
        --delete-subtasks) delete_subtasks="true"; shift 1 ;;
        *) echo "ERROR: unknown option: $1" >&2; exit 2 ;;
      esac
    done

    [ -n "$issue" ] || { echo "ERROR: --issue required" >&2; exit 2; }
    [ -n "$confirm" ] || { echo "ERROR: --confirm-delete required" >&2; exit 2; }
    [ "$confirm" = "$issue" ] || {
      echo "ERROR: --confirm-delete value must exactly match --issue" >&2
      exit 2
    }
    [ "$human_confirm" = "YES" ] || {
      echo "ERROR: high-risk delete requires --human-confirm YES" >&2
      exit 2
    }

    audit_write "issue-delete" "$issue"
    http_request DELETE "$(jira_base)/issue/$(urlencode "$issue")?deleteSubtasks=$delete_subtasks"
    ;;

  issue-search-jql)
    payload_file=""
    while [ "$#" -gt 0 ]; do
      case "$1" in
        --payload-file) payload_file="$2"; shift 2 ;;
        *) echo "ERROR: unknown option: $1" >&2; exit 2 ;;
      esac
    done
    [ -n "$payload_file" ] || { echo "ERROR: --payload-file required" >&2; exit 2; }
    require_file "$payload_file"
    http_request POST "$(jira_base)/search/jql" "$payload_file"
    ;;

  -h|--help|help)
    usage
    ;;

  *)
    echo "ERROR: unknown command: $cmd" >&2
    usage >&2
    exit 2
    ;;
esac
