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

# SkipCalls agent CLI.
# Public, dependency-light helper for agents that should use curl + jq directly.

DEFAULT_SKIPCALLS_API="https://be.skipcalls.com"
DEFAULT_SKIPCALLS_APP_URL="https://app.skipcalls.com"
DEFAULT_SUPABASE_URL="https://efxskoghhbropxdmnyaj.supabase.co"
DEFAULT_SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImVmeHNrb2doaGJyb3B4ZG1ueWFqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzU2MDA5NzUsImV4cCI6MjA1MTE3Njk3NX0.qtcCb-liRlejNj2TLvV6QWwUzvkM9bPUO_TCNjHCAjs"
DEFAULT_AUTH_CALLBACK="https://app.skipcalls.com/auth/callback"

SKIPCALLS_API="${SKIPCALLS_API:-$DEFAULT_SKIPCALLS_API}"
SKIPCALLS_APP_URL="${SKIPCALLS_APP_URL:-$DEFAULT_SKIPCALLS_APP_URL}"
SUPABASE_URL="${SUPABASE_URL:-$DEFAULT_SUPABASE_URL}"
SUPABASE_ANON_KEY="${SUPABASE_ANON_KEY:-$DEFAULT_SUPABASE_ANON_KEY}"
SUPABASE_EMAIL_REDIRECT_TO="${SUPABASE_EMAIL_REDIRECT_TO:-$DEFAULT_AUTH_CALLBACK}"
SKIPCALLS_AUTH_FILE="${SKIPCALLS_AUTH_FILE:-$HOME/.skipcalls-agent-cookies.txt}"

main() {
  require_bin curl
  require_bin jq

  local command="${1:-help}"
  if [ "$#" -gt 0 ]; then
    shift
  fi

  case "$command" in
    help|-h|--help) help ;;
    auth-file) auth_file ;;
    auth-status) auth_status ;;
    signup) signup "$@" ;;
    login) login "$@" ;;
    refresh) refresh_auth_and_print ;;
    voices) voices "$@" ;;
    plans) plans "$@" ;;
    payment-link) payment_link "$@" ;;
    stripe-checkout-link|subscription-checkout) stripe_checkout_link "$@" ;;
    setup-intent) api_print POST "/stripe/setup-intent" "" "$@" ;;
    subscription-current) api_print GET "/subscription/current" "" "$@" ;;
    subscription-create) subscription_create "$@" ;;
    voicemail-get) api_print GET "/voicemail" "" "$@" ;;
    voicemail-create) voicemail_create "$@" ;;
    voicemail-update) voicemail_update "$@" ;;
    voicemail-delete) voicemail_delete "$@" ;;
    number-release) number_release "$@" ;;
    receptionist-onboard) receptionist_onboard "$@" ;;
    calls-outgoing) calls_outgoing "$@" ;;
    calls-incoming) calls_incoming "$@" ;;
    call-get) call_get "$@" ;;
    call-render) call_render "$@" ;;
    call-schedule) call_schedule "$@" ;;
    webhooks) api_print GET "/webhooks" "" "$@" ;;
    webhook-create) webhook_create "$@" ;;
    webhook-test) webhook_test "$@" ;;
    webhook-delete) webhook_delete "$@" ;;
    api-keys) api_print GET "/api-keys" "" "$@" ;;
    api-key-create) api_key_create "$@" ;;
    api-key-revoke) api_key_revoke "$@" ;;
    e2e-local-smoke) e2e_local_smoke "$@" ;;
    *)
      echo "Unknown command: $command" >&2
      echo "Run: $0 help" >&2
      exit 2
      ;;
  esac
}

help() {
  cat <<'EOF'
SkipCalls curl+jq agent CLI

Run from any machine with bash, curl, and jq:
  curl -fsSL https://skipcalls.com/agents/skipcalls-agent-curl-cli.sh -o skipcalls-agent.sh
  chmod +x skipcalls-agent.sh
  ./skipcalls-agent.sh help

Local E2E defaults:
  SKIPCALLS_API=http://localhost:3001
  SKIPCALLS_APP_URL=http://localhost:5173
  SUPABASE_EMAIL_REDIRECT_TO=http://localhost:5173/auth/callback

Auth state:
  Default auth file: ~/.skipcalls-agent-cookies.txt
  Override with: SKIPCALLS_AUTH_FILE=/path/to/file.txt
  The file stores Supabase access_token + refresh_token JSON. Treat it like a browser cookie jar.

Account/auth:
  ./skipcalls-agent.sh signup --email agent@agentmail.to --password 'strong-password'
  ./skipcalls-agent.sh login --email agent@agentmail.to --password 'strong-password'
  ./skipcalls-agent.sh refresh
  ./skipcalls-agent.sh auth-status

Subscription/payment:
  ./skipcalls-agent.sh plans
  ./skipcalls-agent.sh stripe-checkout-link --plan growth --billing-period MONTHLY
  ./skipcalls-agent.sh stripe-checkout-link --plan growth --billing-period MONTHLY | jq -r .url
  ./skipcalls-agent.sh payment-link --plan growth --billing-period MONTHLY
  ./skipcalls-agent.sh setup-intent
  ./skipcalls-agent.sh subscription-create --plan-id <uuid> --payment-method-id pm_card_visa --billing-period MONTHLY
  ./skipcalls-agent.sh subscription-current

Receptionist/voicemail:
  ./skipcalls-agent.sh voices
  ./skipcalls-agent.sh voicemail-get
  ./skipcalls-agent.sh voicemail-create --name Alex --voice coral --operator verizon
  ./skipcalls-agent.sh voicemail-update --id <voicemailId> --data '{"incomingCallInstructions":"Answer professionally."}'
  ./skipcalls-agent.sh voicemail-delete --id <voicemailId>
  ./skipcalls-agent.sh number-release --id <phoneNumberId>
  ./skipcalls-agent.sh receptionist-onboard --name Alex --voice coral --operator verizon --instructions 'Answer professionally.' --forward-type noanswer

Calls:
  ./skipcalls-agent.sh calls-outgoing --limit 10
  ./skipcalls-agent.sh calls-outgoing --limit 10 --format json
  ./skipcalls-agent.sh calls-incoming --limit 10
  ./skipcalls-agent.sh calls-incoming --limit 10 --status PROCESSED --format text
  ./skipcalls-agent.sh call-get --type outgoing --id <callId>
  ./skipcalls-agent.sh call-get --type incoming --id <incomingCallId>
  ./skipcalls-agent.sh call-get --type incoming --id <incomingCallId> --transcript-lines 8
  ./skipcalls-agent.sh call-get --type incoming --id <incomingCallId> --full
  ./skipcalls-agent.sh call-get --type incoming --id <incomingCallId> --format json
  curl "$SKIPCALLS_API/incoming-calls/<incomingCallId>" -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" | ./skipcalls-agent.sh call-render --type incoming
  ./skipcalls-agent.sh call-schedule --phone +12015551234 --goal 'Call and ask if they are available tomorrow.' --max-duration-minutes 3 --force true

Webhooks:
  ./skipcalls-agent.sh webhooks
  ./skipcalls-agent.sh webhook-create --url https://agent.example.com/skipcalls/incoming-call --agent-id <agentId> --receiver-token <receiver-secret>
  ./skipcalls-agent.sh webhook-test --webhook-id <webhookId> --message 'Test callback'
  ./skipcalls-agent.sh webhook-delete --webhook-id <webhookId>

Optional API key:
  ./skipcalls-agent.sh api-key-create --name 'External agent integration'
  ./skipcalls-agent.sh api-keys
  ./skipcalls-agent.sh api-key-revoke --id <apiKeyId>

Dry-run any SkipCalls API command:
  ./skipcalls-agent.sh voicemail-create --name Alex --voice coral --dry-run

Notes:
  - Normal website automation uses the Supabase session token from this auth file.
  - API keys are optional and only needed when the user wants a long-lived server-to-server credential.
  - Voicemail creation mirrors mobile onboarding: create only name/voice/operator first, then patch instructions/greetings/settings.
  - stripe-checkout-link asks the backend for a Stripe-hosted Checkout URL. Send .url to the human, or open it in an agent browser.
  - payment-link prints a shareable app URL fallback. The user/agent opens it in a browser to complete the web payment UI.
  - Use --force-new-subscription true only for a support-approved replacement flow on an account that already has a subscription.
  - Call history/detail commands render a mobile-style text view by default. Call detail output is basic info + summary + transcript only; add --full for safe user-facing task/evaluation results.
  - Add --format json for sanitized API JSON. The CLI strips debug/internal fields such as logs, tool calls, prompt versions, and usage metrics.
  - Before using a freshly downloaded copy, read the script source and tell the human what you checked.
EOF
}

auth_file() {
  echo "$SKIPCALLS_AUTH_FILE"
}

auth_status() {
  if [ ! -f "$SKIPCALLS_AUTH_FILE" ]; then
    echo "No auth file at $SKIPCALLS_AUTH_FILE"
    return 1
  fi

  jq '{
    authFile: "'"$SKIPCALLS_AUTH_FILE"'",
    hasAccessToken: (.access_token | type == "string" and length > 0),
    hasRefreshToken: (.refresh_token | type == "string" and length > 0),
    expiresAtUnix: (.expires_at // null),
    expiresInSeconds: ((.expires_at // 0) - now | floor),
    user: (.user // null)
  }' "$SKIPCALLS_AUTH_FILE"
}

signup() {
  local email password body payload
  email="$(require_arg email "$@")"
  password="$(require_arg password "$@")"

  body="$(jq -nc --arg email "$email" --arg password "$password" '{email:$email,password:$password}')"
  payload="$(supabase_payload POST "/signup?redirect_to=$(urlencode "$SUPABASE_EMAIL_REDIRECT_TO")" "$body" "$@")"
  print_json "$payload"

  if echo "$payload" | jq -e '.access_token? | strings | length > 0' >/dev/null; then
    save_auth "$payload"
    echo "Saved auth to $SKIPCALLS_AUTH_FILE" >&2
  else
    echo "Signup created or confirmation email sent. Poll AgentMail, then open the Supabase confirmation link in the same browser context." >&2
  fi
}

login() {
  local email password body payload
  email="$(require_arg email "$@")"
  password="$(require_arg password "$@")"

  body="$(jq -nc --arg email "$email" --arg password "$password" '{email:$email,password:$password}')"
  payload="$(supabase_payload POST "/token?grant_type=password" "$body" "$@")"
  print_json "$payload"
  save_auth "$payload"
  echo "Saved auth to $SKIPCALLS_AUTH_FILE" >&2
}

refresh_auth_and_print() {
  local payload
  payload="$(refresh_auth)"
  print_json "$payload"
}

refresh_auth() {
  if [ ! -f "$SKIPCALLS_AUTH_FILE" ]; then
    echo "No auth file at $SKIPCALLS_AUTH_FILE" >&2
    exit 1
  fi

  local refresh_token body payload
  refresh_token="$(jq -r '.refresh_token // empty' "$SKIPCALLS_AUTH_FILE")"
  if [ -z "$refresh_token" ]; then
    echo "Auth file has no refresh_token. Run login again." >&2
    exit 1
  fi

  body="$(jq -nc --arg refresh_token "$refresh_token" '{refresh_token:$refresh_token}')"
  payload="$(supabase_payload POST "/token?grant_type=refresh_token" "$body")"
  save_auth "$payload"
  echo "$payload"
}

plans() {
  api_print GET "/subscription/plans?planType=ENTERPRISE" "" "$@"
}

voices() {
  local is_demo query
  is_demo="$(arg is-demo "$@")"
  query="/voices"
  if [ -n "$is_demo" ]; then
    query="$query?isDemo=$(urlencode "$is_demo")"
  fi
  api_print GET "$query" "" "$@"
}

resolve_plan_id() {
  local requested plans_json match
  requested="$1"
  plans_json="$(api_payload GET "/subscription/plans?planType=ENTERPRISE" "")"
  match="$(echo "$plans_json" | jq -c --arg requested "$requested" --arg slug "$(slugify "$requested")" '
    [
      .[]
      | select((.isActive // false) == true)
      | select((.isHidden // false) == false or (.id == $requested) or ((.name | ascii_downcase | gsub("[^a-z0-9]+";"-") | gsub("^-+|-+$";"")) == $slug))
      | select(.id == $requested or (.name | ascii_downcase) == ($requested | ascii_downcase) or ((.name | ascii_downcase | gsub("[^a-z0-9]+";"-") | gsub("^-+|-+$";"")) == $slug))
    ][0] // empty
  ')"

  if [ -z "$match" ]; then
    return 1
  fi

  echo "$match" | jq '{selectedPlan:{id,name,price,yearlyPrice,features,trialAllowed}}' >&2
  echo "$match" | jq -r '.id'
}

payment_link() {
  local requested billing encoded app_url
  requested="$(arg plan "$@")"
  billing="$(arg billing-period "$@")"
  app_url="$(arg app-url "$@")"

  if [ -z "$requested" ]; then
    requested="growth"
  fi
  if [ -z "$billing" ]; then
    billing="MONTHLY"
  fi
  if [ -z "$app_url" ]; then
    app_url="$SKIPCALLS_APP_URL"
  fi

  local plan_param="$requested"
  if has_auth_available && ! has_flag no-fetch "$@"; then
    if plan_param="$(resolve_plan_id "$requested")"; then
      true
    else
      echo "No active visible plan matched '$requested'; printing link with requested slug anyway." >&2
    fi
  fi

  encoded="$(urlencode "$plan_param")"
  echo "${app_url%/}/add-minutes?plan=${encoded}&checkout=1&billingPeriod=${billing}"
}

stripe_checkout_link() {
  local requested plan_id billing_period success_url cancel_url force body
  requested="$(arg plan "$@")"
  plan_id="$(arg plan-id "$@")"
  billing_period="$(arg billing-period "$@")"
  success_url="$(arg success-url "$@")"
  cancel_url="$(arg cancel-url "$@")"
  force="$(arg force-new-subscription "$@")"

  if [ -z "$billing_period" ]; then
    billing_period="MONTHLY"
  fi
  if [ -z "$plan_id" ]; then
    if [ -z "$requested" ]; then requested="growth"; fi
    plan_id="$(resolve_plan_id "$requested")"
  fi

  body="$(jq -nc \
    --arg planId "$plan_id" \
    --arg billingPeriod "$billing_period" \
    --arg successUrl "$success_url" \
    --arg cancelUrl "$cancel_url" \
    --arg forceNewSubscription "$force" '
      {
        planId: $planId,
        billingPeriod: $billingPeriod
      }
      + (if $successUrl == "" then {} else {successUrl: $successUrl} end)
      + (if $cancelUrl == "" then {} else {cancelUrl: $cancelUrl} end)
      + (if $forceNewSubscription == "" then {} else {forceNewSubscription: ($forceNewSubscription == "true" or $forceNewSubscription == "1")} end)
    ')"

  api_print POST "/stripe/subscription-checkout" "$body" "$@"
}

subscription_create() {
  local plan_id payment_method_id billing_period body
  plan_id="$(require_arg plan-id "$@")"
  payment_method_id="$(arg payment-method-id "$@")"
  billing_period="$(arg billing-period "$@")"
  if [ -z "$billing_period" ]; then
    billing_period="MONTHLY"
  fi

  body="$(jq -nc \
    --arg planId "$plan_id" \
    --arg paymentMethodId "$payment_method_id" \
    --arg billingPeriod "$billing_period" '
      {
        planId: $planId,
        billingPeriod: $billingPeriod
      }
      + (if $paymentMethodId == "" then {} else {paymentMethodId: $paymentMethodId} end)
    ')"

  api_print POST "/subscription" "$body" "$@"
}

voicemail_create() {
  local name voice operator body
  name="$(arg name "$@")"
  voice="$(arg voice "$@")"
  operator="$(arg operator "$@")"
  if [ -z "$name" ]; then name="Alex"; fi
  if [ -z "$voice" ]; then voice="coral"; fi

  body="$(jq -nc --arg name "$name" --arg voice "$voice" --arg operator "$operator" '
    {name:$name, voice:$voice}
    + (if $operator == "" then {} else {operator:$operator} end)
  ')"

  api_print POST "/voicemail" "$body" "$@"
}

voicemail_update() {
  local id data bad
  id="$(require_arg id "$@")"
  data="$(require_arg data "$@")"
  ensure_json_object "$data" "--data"

  bad="$(echo "$data" | jq -r '
    ["name","incomingCallInstructions","welcomingVoiceMailMessageForContacts","welcomingVoiceMailMessageForOthers","voice","voiceSpeed","operator","redirectionType","callTransferNumber","transferHours","postCallSmsMessage","allowFollowUps","followUpInstructions","allowIncomingSms","incomingSmsInstructions","autoRespondToSms","smsResponseDelay","useSmsIncomingCallFlow","allowRealtimeSms","requireDataConsent","dataConsentText","backgroundSound","typingSound","accessToKnowledge","enablePostCallActions","postCallActionInstructions","strictSpamFilter","valuableCallRule","closingPhrase","delayAcceptSeconds","voiceRecognition"] as $allowed
    | keys - $allowed
    | .[]
  ')"
  if [ -n "$bad" ]; then
    echo "Refusing unknown voicemail update field(s):" >&2
    echo "$bad" >&2
    exit 1
  fi

  api_print PATCH "/voicemail/$id" "$data" "$@"
}

voicemail_delete() {
  local id
  id="$(require_arg id "$@")"
  api_print DELETE "/voicemail/$id" "" "$@"
}

number_release() {
  local id
  id="$(require_arg id "$@")"
  api_print DELETE "/numbers/$id" "" "$@"
}

receptionist_onboard() {
  local name voice operator instructions contact_greeting other_greeting forward_type voicemail_json voicemail_id patch
  name="$(arg name "$@")"
  voice="$(arg voice "$@")"
  operator="$(arg operator "$@")"
  instructions="$(arg instructions "$@")"
  contact_greeting="$(arg contact-greeting "$@")"
  other_greeting="$(arg other-greeting "$@")"
  forward_type="$(arg forward-type "$@")"

  if [ -z "$name" ]; then name="Alex"; fi
  if [ -z "$voice" ]; then voice="coral"; fi
  if [ -z "$instructions" ]; then
    instructions="Answer professionally, collect the caller name, phone number, reason for calling, and preferred callback time."
  fi
  if [ -z "$contact_greeting" ]; then contact_greeting="Hi, thanks for calling. How can I help today?"; fi
  if [ -z "$other_greeting" ]; then other_greeting="Hi, thanks for calling. May I get your name and what you are calling about?"; fi
  if [ -z "$forward_type" ]; then forward_type="noanswer"; fi

  voicemail_json="$(api_payload GET "/voicemail" "")"
  voicemail_id="$(echo "$voicemail_json" | jq -r '.voicemail.id // empty')"

  if [ -z "$voicemail_id" ]; then
    local create_body
    create_body="$(jq -nc --arg name "$name" --arg voice "$voice" --arg operator "$operator" '
      {name:$name, voice:$voice}
      + (if $operator == "" then {} else {operator:$operator} end)
    ')"
    voicemail_json="$(api_payload POST "/voicemail" "$create_body")"
    voicemail_id="$(echo "$voicemail_json" | jq -r '.id // .voicemail.id // empty')"
  fi

  if [ -z "$voicemail_id" ]; then
    echo "Could not determine voicemail id from response:" >&2
    print_json "$voicemail_json" >&2
    exit 1
  fi

  patch="$(jq -nc \
    --arg incomingCallInstructions "$instructions" \
    --arg welcomingVoiceMailMessageForContacts "$contact_greeting" \
    --arg welcomingVoiceMailMessageForOthers "$other_greeting" \
    --arg redirectionType "$forward_type" \
    '{incomingCallInstructions:$incomingCallInstructions, welcomingVoiceMailMessageForContacts:$welcomingVoiceMailMessageForContacts, welcomingVoiceMailMessageForOthers:$welcomingVoiceMailMessageForOthers, redirectionType:$redirectionType}')"

  api_print PATCH "/voicemail/$voicemail_id" "$patch" "$@"
}

calls_outgoing() {
  local limit offset raw status start_date end_date phone favorites query payload format
  limit="$(arg limit "$@")"; if [ -z "$limit" ]; then limit="10"; fi
  offset="$(arg offset "$@")"; if [ -z "$offset" ]; then offset="0"; fi
  raw="$(arg raw "$@")"
  status="$(arg status "$@")"
  start_date="$(arg start-date "$@")"; if [ -z "$start_date" ]; then start_date="$(arg startDate "$@")"; fi
  end_date="$(arg end-date "$@")"; if [ -z "$end_date" ]; then end_date="$(arg endDate "$@")"; fi
  phone="$(arg phone "$@")"; if [ -z "$phone" ]; then phone="$(arg phone-number "$@")"; fi
  favorites="$(arg favorites "$@")"
  format="$(output_format "$@")"
  query="/calls?limit=$(urlencode "$limit")&offset=$(urlencode "$offset")"
  if [ -n "$raw" ]; then query="$query&raw=$(urlencode "$raw")"; fi
  if [ -n "$status" ]; then query="$query&status=$(urlencode "$status")"; fi
  if [ -n "$start_date" ]; then query="$query&startDate=$(urlencode "$start_date")"; fi
  if [ -n "$end_date" ]; then query="$query&endDate=$(urlencode "$end_date")"; fi
  if [ -n "$phone" ]; then query="$query&phoneNumber=$(urlencode "$phone")"; fi
  if [ -n "$favorites" ]; then query="$query&favorites=$(urlencode "$favorites")"; fi

  payload="$(api_payload GET "$query" "" "$@")"
  if [ "$format" = "json" ]; then
    print_json "$(sanitize_call_payload "$payload")"
  else
    render_call_list "$payload" "outgoing"
  fi
}

calls_incoming() {
  local limit offset raw status start_date end_date phone favorites query payload format
  limit="$(arg limit "$@")"; if [ -z "$limit" ]; then limit="10"; fi
  offset="$(arg offset "$@")"; if [ -z "$offset" ]; then offset="0"; fi
  raw="$(arg raw "$@")"
  status="$(arg status "$@")"
  start_date="$(arg start-date "$@")"; if [ -z "$start_date" ]; then start_date="$(arg startDate "$@")"; fi
  end_date="$(arg end-date "$@")"; if [ -z "$end_date" ]; then end_date="$(arg endDate "$@")"; fi
  phone="$(arg phone "$@")"; if [ -z "$phone" ]; then phone="$(arg phone-number "$@")"; fi
  favorites="$(arg favorites "$@")"
  format="$(output_format "$@")"
  query="/incoming-calls?limit=$(urlencode "$limit")&offset=$(urlencode "$offset")"
  if [ -n "$raw" ]; then query="$query&raw=$(urlencode "$raw")"; fi
  if [ -n "$status" ]; then query="$query&status=$(urlencode "$status")"; fi
  if [ -n "$start_date" ]; then query="$query&startDate=$(urlencode "$start_date")"; fi
  if [ -n "$end_date" ]; then query="$query&endDate=$(urlencode "$end_date")"; fi
  if [ -n "$phone" ]; then query="$query&phoneNumber=$(urlencode "$phone")"; fi
  if [ -n "$favorites" ]; then query="$query&favorites=$(urlencode "$favorites")"; fi

  payload="$(api_payload GET "$query" "" "$@")"
  if [ "$format" = "json" ]; then
    print_json "$(sanitize_call_payload "$payload")"
  else
    render_call_list "$payload" "incoming"
  fi
}

call_get() {
  local id type payload format
  id="$(require_arg id "$@")"
  type="$(arg type "$@")"
  if [ -z "$type" ]; then type="outgoing"; fi
  format="$(output_format "$@")"

  case "$type" in
    outgoing) payload="$(api_payload GET "/calls/$id" "" "$@")" ;;
    incoming) payload="$(api_payload GET "/incoming-calls/$id" "" "$@")" ;;
    *)
      echo "--type must be outgoing or incoming" >&2
      exit 1
      ;;
  esac

  if [ "$format" = "json" ]; then
    print_json "$(sanitize_call_payload "$payload")"
  else
    render_call_detail "$payload" "$type" "$@"
  fi
}

call_render() {
  local type payload format
  type="$(arg type "$@")"
  if [ -z "$type" ]; then type="auto"; fi
  format="$(output_format "$@")"
  payload="$(cat)"
  if [ "$format" = "json" ]; then
    print_json "$(sanitize_call_payload "$payload")"
  else
    render_call_detail "$payload" "$type" "$@"
  fi
}

call_schedule() {
  local phone goal scheduled_at timezone additional_context max_duration retry retry_after force ask_private agent_id allow_transfer transfer_number title first_message cron body
  phone="$(require_arg phone "$@")"
  goal="$(require_arg goal "$@")"
  scheduled_at="$(arg scheduled-at "$@")"
  timezone="$(arg timezone "$@")"
  additional_context="$(arg additional-context "$@")"
  max_duration="$(arg max-duration-minutes "$@")"
  retry="$(arg retry "$@")"
  retry_after="$(arg retry-after-minutes "$@")"
  force="$(arg force "$@")"
  ask_private="$(arg include-private-data "$@")"
  agent_id="$(arg agent-id "$@")"
  allow_transfer="$(arg allow-call-transfer "$@")"
  transfer_number="$(arg call-transfer-number "$@")"
  title="$(arg title "$@")"
  first_message="$(arg first-message "$@")"
  cron="$(arg cron-schedule "$@")"
  if [ -z "$timezone" ]; then timezone="America/Los_Angeles"; fi
  if [ -z "$max_duration" ]; then max_duration="3"; fi
  if [ -z "$force" ]; then force="true"; fi

  body="$(jq -nc \
    --arg phoneNumber "$phone" \
    --arg goal "$goal" \
    --arg scheduledAt "$scheduled_at" \
    --arg timezone "$timezone" \
    --arg additionalContext "$additional_context" \
    --argjson maxDurationMinutes "$max_duration" \
    --arg retry "$retry" \
    --arg retryAfterMinutes "$retry_after" \
    --arg force "$force" \
    --arg includePrivateData "$ask_private" \
    --arg agentId "$agent_id" \
    --arg allowCallTransfer "$allow_transfer" \
    --arg callTransferNumber "$transfer_number" \
    --arg title "$title" \
    --arg firstMessage "$first_message" \
    --arg cronSchedule "$cron" '
      {
        phoneNumber:$phoneNumber,
        goal:$goal,
        timezone:$timezone,
        maxDurationMinutes:$maxDurationMinutes,
        force: ($force == "true" or $force == "1"),
        askForClarification: false
      }
      + (if $scheduledAt == "" then {} else {scheduledAt:$scheduledAt} end)
      + (if $additionalContext == "" then {} else {additionalContext:$additionalContext} end)
      + (if $retry == "" then {} else {doWeNeedToRetry:($retry == "true" or $retry == "1")} end)
      + (if $retryAfterMinutes == "" then {} else {retryAfterMinutes:($retryAfterMinutes|tonumber)} end)
      + (if $includePrivateData == "" then {} else {includePrivateData:($includePrivateData == "true" or $includePrivateData == "1")} end)
      + (if $agentId == "" then {} else {agentId:$agentId} end)
      + (if $allowCallTransfer == "" then {} else {allowCallTransfer:($allowCallTransfer == "true" or $allowCallTransfer == "1")} end)
      + (if $callTransferNumber == "" then {} else {callTransferNumber:$callTransferNumber} end)
      + (if $title == "" then {} else {title:$title} end)
      + (if $firstMessage == "" then {} else {firstMessage:$firstMessage} end)
      + (if $cronSchedule == "" then {} else {cronSchedule:$cronSchedule} end)
    ')"

  api_print POST "/calls/schedule" "$body" "$@"
}

webhook_create() {
  local url agent_id receiver_token headers_json events secret timeout retry name description body
  url="$(require_arg url "$@")"
  agent_id="$(arg agent-id "$@")"
  receiver_token="$(arg receiver-token "$@")"
  headers_json="$(arg headers-json "$@")"
  events="$(arg events "$@")"; if [ -z "$events" ]; then events="INCOMING_CALL_COMPLETED"; fi
  secret="$(arg secret "$@")"
  timeout="$(arg timeout-ms "$@")"; if [ -z "$timeout" ]; then timeout="10000"; fi
  retry="$(arg retry-count "$@")"; if [ -z "$retry" ]; then retry="3"; fi
  name="$(arg name "$@")"; if [ -z "$name" ]; then name="Inbound call processed callback"; fi
  description="$(arg description "$@")"; if [ -z "$description" ]; then description="Send completed inbound call transcript and summary to my external agent."; fi

  if [ -n "$headers_json" ]; then
    ensure_json_object "$headers_json" "--headers-json"
  elif [ -n "$receiver_token" ]; then
    headers_json="$(jq -nc --arg token "$receiver_token" '{Authorization: (if ($token | startswith("Bearer ")) then $token else "Bearer " + $token end)}')"
  else
    headers_json="{}"
  fi

  body="$(jq -nc \
    --arg name "$name" \
    --arg description "$description" \
    --arg url "$url" \
    --arg events "$events" \
    --arg agentId "$agent_id" \
    --argjson headers "$headers_json" \
    --arg secret "$secret" \
    --argjson timeoutMs "$timeout" \
    --argjson retryCount "$retry" '
      {
        name:$name,
        description:$description,
        url:$url,
        method:"POST",
        headers:$headers,
        events: ($events | split(",") | map(select(length > 0))),
        format:"DEFAULT",
        timeoutMs:$timeoutMs,
        retryCount:$retryCount,
        enableSmartFilter:false
      }
      + (if $agentId == "" then {} else {agentIds:($agentId | split(",") | map(select(length > 0)))} end)
      + (if $secret == "" then {} else {secret:$secret} end)
    ')"

  api_print POST "/webhooks" "$body" "$@"
}

webhook_test() {
  local webhook_id message body
  webhook_id="$(require_arg webhook-id "$@")"
  message="$(arg message "$@")"
  if [ -z "$message" ]; then message="Test callback from SkipCalls"; fi
  body="$(jq -nc --arg webhookId "$webhook_id" --arg message "$message" '{webhookId:$webhookId,message:$message}')"
  api_print POST "/webhooks/test" "$body" "$@"
}

webhook_delete() {
  local webhook_id
  webhook_id="$(require_arg webhook-id "$@")"
  api_print DELETE "/webhooks/$webhook_id" "" "$@"
}

api_key_create() {
  local name expires_at body
  name="$(arg name "$@")"; if [ -z "$name" ]; then name="External agent integration"; fi
  expires_at="$(arg expires-at "$@")"
  body="$(jq -nc --arg name "$name" --arg expiresAt "$expires_at" '{name:$name} + (if $expiresAt == "" then {} else {expiresAt:$expiresAt} end)')"
  api_print POST "/api-keys" "$body" "$@"
}

api_key_revoke() {
  local id body
  id="$(require_arg id "$@")"
  body="$(jq -nc --arg id "$id" '{id:$id}')"
  api_print POST "/api-keys/revoke" "$body" "$@"
}

e2e_local_smoke() {
  local email password phone
  export SKIPCALLS_API="${SKIPCALLS_API:-http://localhost:3001}"
  export SKIPCALLS_APP_URL="${SKIPCALLS_APP_URL:-http://localhost:5173}"
  export SUPABASE_EMAIL_REDIRECT_TO="${SUPABASE_EMAIL_REDIRECT_TO:-http://localhost:5173/auth/callback}"

  email="$(arg email "$@")"
  password="$(arg password "$@")"
  phone="$(arg phone "$@")"

  echo "Health:"
  curl -fsS "${SKIPCALLS_API%/}/health" | jq .

  if [ -n "$email" ] && [ -n "$password" ]; then
    echo "Login:"
    login --email "$email" --password "$password"
  fi

  echo "Auth status:"
  auth_status || true

  echo "Payment link:"
  payment_link --plan "$(arg plan "$@")" --billing-period MONTHLY --app-url "$SKIPCALLS_APP_URL" || true

  echo "Plans:"
  plans --dry-run

  echo "Subscription current:"
  api_print GET "/subscription/current" "" --dry-run

  echo "Voicemail create request:"
  voicemail_create --name "Agent Test" --voice coral --operator verizon --dry-run

  echo "Voicemail update request:"
  voicemail_update --id "00000000-0000-4000-8000-000000000000" --data '{"incomingCallInstructions":"Answer professionally and collect caller name, phone, reason, and callback time."}' --dry-run

  echo "Outgoing calls request:"
  calls_outgoing --limit 5 --dry-run

  echo "Incoming calls request:"
  calls_incoming --limit 5 --dry-run

  if [ -n "$phone" ]; then
    echo "Outbound call schedule request:"
    call_schedule --phone "$phone" --goal "Test local agent CLI request. Ask if this number can receive calls." --max-duration-minutes 1 --force true --dry-run
  fi
}

output_format() {
  local format
  if has_flag json "$@"; then
    echo "json"
    return 0
  fi

  format="$(arg format "$@")"
  if [ -z "$format" ]; then
    echo "text"
    return 0
  fi

  case "$format" in
    json|raw) echo "json" ;;
    text|mobile|pretty) echo "text" ;;
    *)
      echo "--format must be text or json" >&2
      exit 2
      ;;
  esac
}

sanitize_call_payload() {
  local payload="$1"
  if ! echo "$payload" | jq -e . >/dev/null 2>&1; then
    echo "$payload"
    return 0
  fi

  echo "$payload" | jq '
    def strip_internal:
      if type == "object" then
        del(
          .diagnostics,
          .logs,
          .toolCalls,
          .promptVersions,
          .usageMetrics,
          .livekitRoomToken
        )
        | if (.calls | type) == "array" then .calls |= map(strip_internal) else . end
      elif type == "array" then
        map(strip_internal)
      else
        .
      end;
    strip_internal
  '
}

render_call_list() {
  local payload="$1"
  local fallback_type="$2"

  if ! echo "$payload" | jq -e . >/dev/null 2>&1; then
    echo "$payload"
    return 0
  fi

  echo "$payload" | jq -r --arg fallbackType "$fallback_type" '
    def clean:
      if . == null then ""
      else tostring | split("\n") | join(" ") | split("\r") | join(" ") | split("\t") | join(" ")
      end;
    def trunc($n):
      clean as $value
      | if ($value | length) > $n then ($value[0:($n - 3)] + "...") else $value end;
    def calls:
      if type == "array" then .
      elif (.calls | type) == "array" then .calls
      else []
      end;
    def total:
      if type == "array" then length
      elif .total? != null then .total
      elif .pagination?.total != null then .pagination.total
      else (calls | length)
      end;
    def call_kind($call):
      if $fallbackType == "incoming" or $fallbackType == "outgoing" then $fallbackType
      elif $call.callType? == "incoming" or $call.callType? == "outgoing" then $call.callType
      elif $call.phoneNumberFrom? != null then "incoming"
      else "outgoing"
      end;
    def call_date($call):
      if call_kind($call) == "incoming" then ($call.createdAt // $call.startedAt // "")
      else ($call.scheduledAt // $call.createdAt // "")
      end;
    def call_phone($call):
      if call_kind($call) == "incoming" then ($call.phoneNumberFrom // "Unknown")
      else ($call.phoneNumber // "Unknown")
      end;
    def call_title($call):
      if call_kind($call) == "incoming" then
        ($call.callerName // $call.title // $call.finalSummary // "Incoming call")
      else
        ($call.placeWhereToCall // $call.title // $call.finalSummary // $call.goal // "Outgoing call")
      end;
    def call_minutes($call):
      if ($call.minutesUsed // null) == null then ""
      else " | " + (($call.minutesUsed | tostring) + " min")
      end;
    def row($call):
      "- [" + call_kind($call) + "] "
      + (call_date($call) | clean) + " | "
      + (($call.status // "UNKNOWN") | tostring) + " | "
      + (call_phone($call) | clean) + " | "
      + (call_title($call) | trunc(96))
      + call_minutes($call)
      + " | id=" + (($call.id // "") | tostring);

    [
      "SkipCalls " + (if $fallbackType == "incoming" then "Incoming" elif $fallbackType == "outgoing" then "Outgoing" else "Call" end) + " History",
      "Showing " + ((calls | length) | tostring) + " of " + (total | tostring),
      ""
    ]
    + (calls | map(row(.)))
    | .[]
  '
}

render_call_detail() {
  local payload="$1"
  local fallback_type="$2"
  shift 2 || true

  local transcript_lines include_full
  transcript_lines="$(arg transcript-lines "$@")"
  if [ -z "$transcript_lines" ]; then transcript_lines="0"; fi
  include_full=""
  if has_flag full "$@"; then include_full="true"; fi

  if ! echo "$payload" | jq -e . >/dev/null 2>&1; then
    echo "$payload"
    return 0
  fi

  echo "$payload" | jq -r \
    --arg fallbackType "$fallback_type" \
    --arg transcriptLines "$transcript_lines" \
    --arg includeFull "$include_full" '
    def clean:
      if . == null then ""
      else tostring | split("\n") | join(" ") | split("\r") | join(" ") | split("\t") | join(" ")
      end;
    def nonblank: clean | select(length > 0);
    def line($label; $value):
      ($value | clean) as $clean
      | if ($clean | length) > 0 then ($label + ": " + $clean) else empty end;
    def section($title; $items):
      ($items | map(select((. | clean | length) > 0))) as $cleanItems
      | if ($cleanItems | length) > 0 then ["", $title, "------------------------------"] + $cleanItems else [] end;
    def kind:
      if $fallbackType == "incoming" or $fallbackType == "outgoing" then $fallbackType
      elif .callType? == "incoming" or .callType? == "outgoing" then .callType
      elif .phoneNumberFrom? != null then "incoming"
      else "outgoing"
      end;
    def date_value:
      if kind == "incoming" then (.createdAt // .startedAt // "")
      else (.scheduledAt // .createdAt // "")
      end;
    def to_number:
      if kind == "incoming" then (.phoneNumberTo.phoneNumber? // .phoneNumberTo // "Your number")
      else (.phoneNumber // "")
      end;
    def from_number:
      if kind == "incoming" then (.phoneNumberFrom // "")
      else (.callFrom // "")
      end;
    def duration:
      if (.minutesUsed // null) == null then ""
      else ((.minutesUsed | tostring) + " minutes")
      end;
    def result_label:
      if . == null then ""
      elif type == "string" then clean
      else tojson
      end;
    def transcript_items:
      ($transcriptLines | tonumber? // 0) as $limit
      | if (.roleplayTranscript | type) == "array" then
          (.roleplayTranscript as $all
          | (if $limit > 0 then $all[0:$limit] else $all end)
          | map("  " + ((.from // "participant") | tostring) + ": " + ((.text // .content // "") | clean)))
          + (if $limit > 0 and ((.roleplayTranscript | length) > $limit)
             then ["  ... " + (((.roleplayTranscript | length) - $limit) | tostring) + " more transcript messages"]
             else []
             end)
        elif (.transcript // .transcription // "") != "" then
          ["  " + ((.transcript // .transcription) | clean)]
        else []
        end;
    def evaluation_items:
      (.evaluations // [])
      | map(
          ((.type? // .result? // .passed? // .score?) | result_label) as $value
          | "  - "
          + ((.evaluation.name? // .evaluation.title? // .name // "evaluation") | clean)
          + (if $value == "" then "" else ": " + $value end)
          + (if (.rationale // "") != "" then " | " + (.rationale | clean) else "" end)
        );
    def task_items:
      ((.taskResults // []) + (.tasks // []))
      | map(
          "  - "
          + ((.taskDefinition.title? // .title // .name // "task") | clean)
          + (if (.status // "") != "" then " | " + (.status | tostring) else "" end)
          + (if (.result // .output // .summary // "") != "" then " | " + ((.result // .output // .summary) | result_label) else "" end)
        );

    [
      "SkipCalls Call Details",
      "======================",
      line("Type"; if kind == "incoming" then "Incoming Call" else "Outgoing Call" end),
      line("ID"; .id),
      line("Status"; .status),
      line("Date"; date_value),
      line("Duration"; duration),
      line("From"; from_number),
      line("To"; to_number),
      line("Caller"; .callerName),
      line("Agent"; .agent.name?),
      line("Title"; .title),
      line("Goal"; .goal),
      line("Recording"; .recordingUrl),
      line("Public Share"; if (.shareHash // "") != "" then ("https://app.skipcalls.com/share.html?id=" + (.id | tostring) + "&hash=" + (.shareHash | tostring)) else "" end)
    ]
    + section("Summary"; [
        line("Short"; .shortFinalSummary),
        ((.finalSummary // "") | clean),
        line("Recommended Next Action"; .recommendedNextAction)
    ])
    + section("Transcript"; transcript_items)
    + (if ($includeFull == "true" or $includeFull == "1") then
        section("Evaluations"; evaluation_items)
        + section("Tasks"; task_items)
      else [] end)
    | .[]
  '
}

api_print() {
  local payload
  payload="$(api_payload "$@")"
  print_json "$payload"
}

api_payload() {
  local method="$1"
  local path="$2"
  local body="${3:-}"
  shift 3 || true

  local url token
  url="${SKIPCALLS_API%/}$path"

  if has_flag dry-run "$@"; then
    print_curl "$method" "$url" "Authorization: Bearer <token>" "$body" >&2
    echo "{}"
    return 0
  fi

  token="$(auth_token)"

  if [ -n "$body" ]; then
    http_payload "$method" "$url" "$body" \
      -H "Authorization: Bearer $token" \
      -H "Content-Type: application/json"
  else
    http_payload "$method" "$url" "" \
      -H "Authorization: Bearer $token"
  fi
}

supabase_payload() {
  local method="$1"
  local path="$2"
  local body="${3:-}"
  shift 3 || true

  local url
  url="${SUPABASE_URL%/}/auth/v1$path"

  if has_flag dry-run "$@"; then
    print_curl "$method" "$url" "apikey: <supabase-anon-key>" "$body" >&2
    echo "{}"
    return 0
  fi

  http_payload "$method" "$url" "$body" \
    -H "apikey: $SUPABASE_ANON_KEY" \
    -H "Content-Type: application/json"
}

http_payload() {
  local method="$1"
  local url="$2"
  local body="$3"
  shift 3

  local response status payload
  if [ -n "$body" ]; then
    response="$(curl -sS -X "$method" "$url" "$@" --data "$body" -w $'\n%{http_code}')"
  else
    response="$(curl -sS -X "$method" "$url" "$@" -w $'\n%{http_code}')"
  fi

  status="${response##*$'\n'}"
  payload="${response%$'\n'*}"

  if [ "$status" -lt 200 ] || [ "$status" -ge 300 ]; then
    print_json "$payload" >&2
    echo "HTTP $status: $method $url" >&2
    exit 1
  fi

  echo "$payload"
}

auth_token() {
  if [ -n "${SKIPCALLS_SESSION_TOKEN:-}" ]; then
    echo "$SKIPCALLS_SESSION_TOKEN"
    return 0
  fi
  if [ -n "${SUPABASE_ACCESS_TOKEN:-}" ]; then
    echo "$SUPABASE_ACCESS_TOKEN"
    return 0
  fi
  if [ -f "$SKIPCALLS_AUTH_FILE" ]; then
    local expires_at now refresh_token
    expires_at="$(jq -r '.expires_at // 0' "$SKIPCALLS_AUTH_FILE")"
    refresh_token="$(jq -r '.refresh_token // empty' "$SKIPCALLS_AUTH_FILE")"
    now="$(jq -nr 'now | floor')"
    if [ -n "$refresh_token" ] && [ "$expires_at" -gt 0 ] && [ "$expires_at" -le $((now + 60)) ]; then
      refresh_auth >/dev/null
    fi
    jq -r '.access_token // empty' "$SKIPCALLS_AUTH_FILE"
    return 0
  fi
  if [ -n "${SKIPCALLS_API_KEY:-}" ]; then
    echo "$SKIPCALLS_API_KEY"
    return 0
  fi

  echo "No auth token. Run login first, set SKIPCALLS_SESSION_TOKEN, or set optional SKIPCALLS_API_KEY." >&2
  exit 1
}

has_auth_available() {
  [ -n "${SKIPCALLS_SESSION_TOKEN:-}" ] || [ -n "${SUPABASE_ACCESS_TOKEN:-}" ] || [ -f "$SKIPCALLS_AUTH_FILE" ] || [ -n "${SKIPCALLS_API_KEY:-}" ]
}

save_auth() {
  local payload="$1"
  if ! echo "$payload" | jq -e '.access_token? | strings | length > 0' >/dev/null; then
    echo "Response did not include access_token; auth file not updated." >&2
    return 1
  fi

  mkdir -p "$(dirname "$SKIPCALLS_AUTH_FILE")"
  umask 077
  echo "$payload" | jq '{
    access_token,
    refresh_token,
    token_type,
    expires_in,
    expires_at: (now + (.expires_in // 3600) | floor),
    user: (.user // null)
  }' > "$SKIPCALLS_AUTH_FILE"
}

arg() {
  local name="$1"
  shift
  while [ "$#" -gt 0 ]; do
    case "$1" in
      "--$name")
        if [ "$#" -lt 2 ]; then
          echo "Missing value for --$name" >&2
          exit 2
        fi
        echo "$2"
        return 0
        ;;
      "--$name="*)
        echo "${1#*=}"
        return 0
        ;;
    esac
    shift
  done
}

require_arg() {
  local name="$1"
  shift
  local value
  value="$(arg "$name" "$@")"
  if [ -z "$value" ]; then
    echo "Missing --$name" >&2
    exit 2
  fi
  echo "$value"
}

has_flag() {
  local name="$1"
  shift
  while [ "$#" -gt 0 ]; do
    if [ "$1" = "--$name" ]; then
      return 0
    fi
    shift
  done
  return 1
}

ensure_json_object() {
  local data="$1"
  local label="$2"
  if ! echo "$data" | jq -e 'type == "object"' >/dev/null; then
    echo "$label must be a JSON object" >&2
    exit 2
  fi
}

print_json() {
  local payload="$1"
  if [ -z "$payload" ]; then
    return 0
  fi
  if echo "$payload" | jq . >/dev/null 2>&1; then
    echo "$payload" | jq .
  else
    echo "$payload"
  fi
}

print_curl() {
  local method="$1"
  local url="$2"
  local auth_header="$3"
  local body="${4:-}"

  if [ -n "$body" ]; then
    cat <<EOF
curl -sS -X '$method' '$url' \\
  -H '$auth_header' \\
  -H 'Content-Type: application/json' \\
  --data @- <<'JSON'
$(echo "$body" | jq .)
JSON
EOF
  else
    cat <<EOF
curl -sS -X '$method' '$url' \\
  -H '$auth_header'
EOF
  fi
}

urlencode() {
  jq -nr --arg value "$1" '$value | @uri'
}

slugify() {
  jq -nr --arg value "$1" '$value | ascii_downcase | gsub("[^a-z0-9]+";"-") | gsub("^-+|-+$";"")'
}

require_bin() {
  if ! command -v "$1" >/dev/null 2>&1; then
    echo "Missing required command: $1" >&2
    exit 127
  fi
}

main "$@"
