#!/usr/bin/env bash
#
# fatoora-full-workflow.sh — Complete Fatoora Organization API / Partner API workflow:
# OAuth2 authentication (client_credentials), invoice submission (JSON or XML
# TEIF), SEAL signature trigger, asynchronous job polling until TTN acceptance,
# followed by final signed XML and PDF retrieval.
#
# Documentation: https://help.fatoora.tn/integration/organisation-api
#                  https://help.fatoora.tn/integration/partner-api
#
# Dependencies: bash, curl, jq
#
# Only SEAL signatures are supported: DigiGO requires a user to complete an
# OAuth/OTP flow in a browser, which cannot be fully automated using curl alone
# (see --help for details).
#
# Examples:
#
#   # Organization API — using your own API credentials
#   ./fatoora-full-workflow.sh --mode organization \
#     --client-id "$CLIENT_ID" --client-secret "$CLIENT_SECRET" \
#     --format xml --invoice-file facture.xml --signature-type seal \
#     --base-url https://business.fatoora.tn
#
#   # Partner API — invoice on behalf of a selected authorized customer organization
#   ./fatoora-full-workflow.sh --mode partner \
#     --client-id "$CLIENT_ID" --client-secret "$CLIENT_SECRET" \
#     --format json --invoice-file facture.json --signature-type seal
#
#   # Partner API — interactively select the customer organization
#   ./fatoora-full-workflow.sh --mode partner --interactive \
#     --client-id "$CLIENT_ID" --client-secret "$CLIENT_SECRET" \
#     --format json --invoice-file facture.json --signature-type seal
#
#   # Generate a JSON invoice inside the script; no invoice file is needed
#   ./fatoora-full-workflow.sh --mode organization \
#     --client-id "$CLIENT_ID" --client-secret "$CLIENT_SECRET" \
#     --format json --generate-invoice --signature-type seal \
#     --invoice-number INV-2026-001 --seller-tax-id 1234567RAM000 \
#     --seller-name "Seller Ltd" --buyer-tax-id 7654321RAM000 \
#     --buyer-name "Customer Ltd" --item-description "Consulting service" \
#     --quantity 1 --unit-price 100 --vat-rate 19
#
set -euo pipefail

# ---------------------------------------------------------------------------
# Default values
# ---------------------------------------------------------------------------
BASE_URL="${FATOORA_API_BASE_URL:-https://business.sandbox.fatoora.tn}"
MODE=""
CLIENT_ID=""
CLIENT_SECRET=""
INVOICE_FORMAT=""
INVOICE_FILE=""
SIGNATURE_TYPE="seal"
OUTPUT_DIR="./fatoora-output"
POLL_INTERVAL=5
POLL_TIMEOUT=600
INTERACTIVE=0
REQUESTED_ORG_ID=""
REQUESTED_ORG_TAX_ID=""
ORGANIZATION_ID=""
ORGANIZATION_NAME=""
ORGANIZATION_TAX_ID=""
INSECURE=0
DEBUG=0
GENERATE_INVOICE=0
INVOICE_NUMBER=""
INVOICE_DATE=""
CURRENCY="TND"
SELLER_TAX_ID=""
SELLER_NAME=""
BUYER_TAX_ID=""
BUYER_NAME=""
ITEM_DESCRIPTION=""
QUANTITY="1"
UNIT_PRICE="0"
VAT_RATE="19"
SELLER_ADDRESS_DESC="Head office"
SELLER_STREET="N/A"
SELLER_CITY="Tunis"
SELLER_POSTAL_CODE="1000"
SELLER_PHONE="00000000"
SELLER_CONTACT_ID="CONTACT-1"
SELLER_CONTACT_NAME="Seller contact"
SELLER_EMAIL="contact@example.com"
BUYER_ADDRESS_DESC="Head office"
BUYER_STREET="N/A"
BUYER_CITY="Tunis"
BUYER_POSTAL_CODE="1000"
ITEM_CODE="ITEM-1"
ITEM_ID="1"
ITEM_UNIT="C62"
PAYMENT_CONDITION_CODE="I-10"
STAMP_DUTY="0"

# ---------------------------------------------------------------------------
# Colored output (disabled when stdout is not a terminal)
# ---------------------------------------------------------------------------
if [[ -t 1 ]]; then
  C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_BLUE=$'\033[34m'
  C_CYAN=$'\033[36m'; C_MAGENTA=$'\033[35m'; C_DIM=$'\033[2m'; C_BOLD=$'\033[1m'; C_RESET=$'\033[0m'
else
  C_RED=""; C_GREEN=""; C_YELLOW=""; C_BLUE=""; C_CYAN=""; C_MAGENTA=""; C_DIM=""; C_BOLD=""; C_RESET=""
fi

log_step()  { printf "\n%s==> %s%s\n" "${C_BLUE}" "$*" "${C_RESET}"; }
log_info()  { printf "%s\n" "$*"; }
log_ok()    { printf "%s[OK]%s %s\n" "${C_GREEN}" "${C_RESET}" "$*"; }
log_warn()  { printf "%s[WARNING]%s %s\n" "${C_YELLOW}" "${C_RESET}" "$*" >&2; }
log_error() { printf "%s[ERROR]%s %s\n" "${C_RED}" "${C_RESET}" "$*" >&2; }
die()       { log_error "$*"; exit 1; }

usage() {
  cat <<'EOF'
Usage: fatoora-full-workflow.sh [OPTIONS]

Complete Fatoora workflow: authentication -> invoice submission -> SEAL signature
-> TTN job polling -> signed XML and PDF download.

Required options:
  -m, --mode <organization|partner>   API access mode
  -i, --client-id <id>                API key client_id
  -s, --client-secret <secret>        API key client_secret
  -f, --format <xml|json>             Invoice format to submit
  -t, --signature-type <seal>         Signature type (default: seal)

Invoice input:
  -F, --invoice-file <path>           Optional existing TEIF XML or JSON invoice file
      --generate-invoice              Explicitly generate the invoice inside the script
                                      using the fields below

  When --invoice-file is omitted, invoice generation is enabled automatically.
  Missing invoice fields are requested interactively when running in a terminal.

Generated invoice fields:
      --invoice-number <value>        Invoice number
      --invoice-date <YYYY-MM-DD>     Invoice date (default: current date)
      --currency <code>               Currency code (default: TND)
      --seller-tax-id <value>         Seller tax identifier: 7 digits + 3 letters + 3 digits
                                      Example: 1234567RAM000
      --seller-name <value>           Seller legal name
      --buyer-tax-id <value>          Buyer tax identifier: 7 digits + 3 letters + 3 digits
                                      Example: 7654321RAM000
      --buyer-name <value>            Buyer legal name
      --item-description <value>      Description of the generated invoice line
      --quantity <number>             Quantity (default: 1)
      --unit-price <number>           Unit price excluding VAT
      --vat-rate <number>             VAT percentage (default: 19)
      --seller-address-desc <value>   Seller address description (default: Head office)
      --seller-street <value>         Seller street (default: N/A)
      --seller-city <value>           Seller city (default: Tunis)
      --seller-postal-code <value>    Seller postal code (default: 1000)
      --seller-phone <value>          Seller phone (default: 00000000)
      --seller-contact-id <value>     Seller contact identifier (default: CONTACT-1)
      --seller-contact-name <value>   Seller contact name (default: Seller contact)
      --seller-email <value>          Seller email (default: contact@example.com)
      --buyer-address-desc <value>    Buyer address description (default: Head office)
      --buyer-street <value>          Buyer street (default: N/A)
      --buyer-city <value>            Buyer city (default: Tunis)
      --buyer-postal-code <value>     Buyer postal code (default: 1000)
      --item-code <value>             Item code (default: ITEM-1)
      --item-id <value>               Item identifier (default: 1)
      --item-unit <value>             UN/ECE unit code (default: C62)
      --payment-condition-code <v>    TEIF payment condition code (default: I-10)
      --stamp-duty <number>           Stamp duty amount (default: 0)

Partner mode options:
      --org-id <uuid>                 Select an authorized organization by id
      --org-tax-id <value>            Select an authorized organization by tax id
      --interactive                   Display authorized customer organizations and
                                      ask the user to select one. Without an explicit selector,
                                      automatic selection is allowed only when exactly
                                      one organization is authorized.
                                      The script can only invoice for organizations
                                      that have already authorized the partner.

Other options:
  -u, --base-url <url>                API base URL. Default:
                                      https://business.sandbox.fatoora.tn or the
                                      FATOORA_API_BASE_URL environment variable.
                                      Production: https://business.fatoora.tn
  -o, --output-dir <directory>        Output directory for signed XML and PDF
                                      (default: ./fatoora-output)
      --poll-interval <seconds>       Delay between job checks (default: 5)
      --poll-timeout <seconds>        Maximum polling duration (default: 600)
  -d, --debug                         Print full curl commands, HTTP status codes,
                                      and complete server responses in color to stderr
  -k, --insecure                      Disable TLS verification (curl -k); local tests only
  -h, --help                          Show this help

Why only SEAL is supported:
  DigiGO requires a user to complete an OAuth/OTP flow in a browser. The job waits in
  WAITING_DIGIGO_AUTH until that action is completed in the Fatoora interface, so the
  workflow cannot be fully automated using curl alone.
EOF
}

# ---------------------------------------------------------------------------
# Argument parsing
# ---------------------------------------------------------------------------
parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      -m|--mode) MODE="${2:-}"; shift 2 ;;
      -i|--client-id) CLIENT_ID="${2:-}"; shift 2 ;;
      -s|--client-secret) CLIENT_SECRET="${2:-}"; shift 2 ;;
      -f|--format) INVOICE_FORMAT="${2:-}"; shift 2 ;;
      -F|--invoice-file) INVOICE_FILE="${2:-}"; shift 2 ;;
      --generate-invoice) GENERATE_INVOICE=1; shift ;;
      --invoice-number) INVOICE_NUMBER="${2:-}"; shift 2 ;;
      --invoice-date) INVOICE_DATE="${2:-}"; shift 2 ;;
      --currency) CURRENCY="${2:-}"; shift 2 ;;
      --seller-tax-id) SELLER_TAX_ID="${2:-}"; shift 2 ;;
      --seller-name) SELLER_NAME="${2:-}"; shift 2 ;;
      --buyer-tax-id) BUYER_TAX_ID="${2:-}"; shift 2 ;;
      --buyer-name) BUYER_NAME="${2:-}"; shift 2 ;;
      --item-description) ITEM_DESCRIPTION="${2:-}"; shift 2 ;;
      --quantity) QUANTITY="${2:-}"; shift 2 ;;
      --unit-price) UNIT_PRICE="${2:-}"; shift 2 ;;
      --vat-rate) VAT_RATE="${2:-}"; shift 2 ;;
      --seller-address-desc) SELLER_ADDRESS_DESC="${2:-}"; shift 2 ;;
      --seller-street) SELLER_STREET="${2:-}"; shift 2 ;;
      --seller-city) SELLER_CITY="${2:-}"; shift 2 ;;
      --seller-postal-code) SELLER_POSTAL_CODE="${2:-}"; shift 2 ;;
      --seller-phone) SELLER_PHONE="${2:-}"; shift 2 ;;
      --seller-contact-id) SELLER_CONTACT_ID="${2:-}"; shift 2 ;;
      --seller-contact-name) SELLER_CONTACT_NAME="${2:-}"; shift 2 ;;
      --seller-email) SELLER_EMAIL="${2:-}"; shift 2 ;;
      --buyer-address-desc) BUYER_ADDRESS_DESC="${2:-}"; shift 2 ;;
      --buyer-street) BUYER_STREET="${2:-}"; shift 2 ;;
      --buyer-city) BUYER_CITY="${2:-}"; shift 2 ;;
      --buyer-postal-code) BUYER_POSTAL_CODE="${2:-}"; shift 2 ;;
      --item-code) ITEM_CODE="${2:-}"; shift 2 ;;
      --item-id) ITEM_ID="${2:-}"; shift 2 ;;
      --item-unit) ITEM_UNIT="${2:-}"; shift 2 ;;
      --payment-condition-code) PAYMENT_CONDITION_CODE="${2:-}"; shift 2 ;;
      --stamp-duty) STAMP_DUTY="${2:-}"; shift 2 ;;
      -t|--signature-type) SIGNATURE_TYPE="${2:-}"; shift 2 ;;
      --org-id) REQUESTED_ORG_ID="${2:-}"; shift 2 ;;
      --org-tax-id) REQUESTED_ORG_TAX_ID="${2:-}"; shift 2 ;;
      --interactive) INTERACTIVE=1; shift ;;
      -u|--base-url) BASE_URL="${2:-}"; shift 2 ;;
      -o|--output-dir) OUTPUT_DIR="${2:-}"; shift 2 ;;
      --poll-interval) POLL_INTERVAL="${2:-}"; shift 2 ;;
      --poll-timeout) POLL_TIMEOUT="${2:-}"; shift 2 ;;
      -k|--insecure) INSECURE=1; shift ;;
      -d|--debug) DEBUG=1; shift ;;
      -h|--help) usage; exit 0 ;;
      *) die "Unknown option: $1 (see --help)" ;;
    esac
  done
}

validate_args() {
  [[ -n "$MODE" ]] || die "--mode is required (organization|partner)"
  case "$MODE" in
    organization|partner) ;;
    *) die "--mode must be 'organization' or 'partner' (received: $MODE)" ;;
  esac

  [[ -n "$CLIENT_ID" ]] || die "--client-id is required"
  [[ -n "$CLIENT_SECRET" ]] || die "--client-secret is required"
  if [[ -n "$REQUESTED_ORG_ID" && -n "$REQUESTED_ORG_TAX_ID" ]]; then
    die "Use either --org-id or --org-tax-id, not both"
  fi
  if [[ "$MODE" != "partner" && ( -n "$REQUESTED_ORG_ID" || -n "$REQUESTED_ORG_TAX_ID" ) ]]; then
    die "--org-id and --org-tax-id are only valid in partner mode"
  fi

  [[ -n "$INVOICE_FORMAT" ]] || die "--format is required (xml|json)"
  case "$INVOICE_FORMAT" in
    xml|json) ;;
    *) die "--format must be 'xml' or 'json' (received: $INVOICE_FORMAT)" ;;
  esac

  if [[ -n "$INVOICE_FILE" && "$GENERATE_INVOICE" == "1" ]]; then
    die "Use either --invoice-file or --generate-invoice, not both"
  fi

  if [[ -z "$INVOICE_FILE" ]]; then
    GENERATE_INVOICE=1
  else
    [[ -f "$INVOICE_FILE" ]] || die "Invoice file not found: $INVOICE_FILE"
  fi

  SIGNATURE_TYPE="$(printf '%s' "$SIGNATURE_TYPE" | tr '[:upper:]' '[:lower:]')"
  if [[ "$SIGNATURE_TYPE" != "seal" ]]; then
    die "This script only supports 'seal' for --signature-type (received: $SIGNATURE_TYPE). DigiGO requires a user action in a browser (OAuth/OTP) and cannot be controlled by this script in a non-interactive workflow."
  fi

  [[ "$POLL_INTERVAL" =~ ^[0-9]+$ ]] || die "--poll-interval must be a positive integer"
  [[ "$POLL_TIMEOUT" =~ ^[0-9]+$ ]] || die "--poll-timeout must be a positive integer"

  BASE_URL="${BASE_URL%/}"
}

need_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
}

build_curl_opts() {
  CURL_OPTS=(-sS)
  if [[ "$INSECURE" == "1" ]]; then
    CURL_OPTS+=(-k)
  fi
}


debug_curl_command() {
  [[ "$DEBUG" == "1" ]] || return 0
  local -a command=(curl "$@")
  local rendered="" arg quoted
  for arg in "${command[@]}"; do
    if [[ "$arg" == client_secret=* ]]; then
      arg="client_secret=***"
    elif [[ "$arg" == "Authorization: Bearer "* ]]; then
      arg="Authorization: Bearer ***"
    fi
    printf -v quoted '%q' "$arg"
    rendered+="${quoted} "
  done
  printf '\n%s%s[DEBUG REQUEST]%s\n%s%s%s\n' \
    "$C_BOLD" "$C_MAGENTA" "$C_RESET" "$C_CYAN" "$rendered" "$C_RESET" >&2
}

debug_http_response() {
  local code="$1" response_file="$2" label="${3:-Server response}"
  [[ "$DEBUG" == "1" ]] || return 0

  printf '%s%s[DEBUG RESPONSE]%s %s — HTTP %s%s%s\n' \
    "$C_BOLD" "$C_MAGENTA" "$C_RESET" "$label" "$C_YELLOW" "$code" "$C_RESET" >&2

  if [[ ! -f "$response_file" || ! -s "$response_file" ]]; then
    printf '%s(empty response body)%s\n' "$C_DIM" "$C_RESET" >&2
    return 0
  fi

  if jq -e . "$response_file" >/dev/null 2>&1; then
    jq . "$response_file" 2>/dev/null \
      | while IFS= read -r line; do printf '%s%s%s\n' "$C_GREEN" "$line" "$C_RESET" >&2; done
  else
    while IFS= read -r line; do printf '%s%s%s\n' "$C_GREEN" "$line" "$C_RESET" >&2; done < "$response_file"
  fi
}

debug_download_response() {
  local code="$1" output_file="$2"
  [[ "$DEBUG" == "1" ]] || return 0
  local size=0
  [[ -f "$output_file" ]] && size="$(wc -c < "$output_file" | tr -d ' ')"
  printf '%s%s[DEBUG RESPONSE]%s PDF download — HTTP %s%s%s, file=%s, bytes=%s\n' \
    "$C_BOLD" "$C_MAGENTA" "$C_RESET" "$C_YELLOW" "$code" "$C_RESET" "$output_file" "$size" >&2
}


prompt_value() {
  local variable_name="$1" prompt="$2" default_value="${3:-}" current_value
  current_value="${!variable_name:-}"
  [[ -n "$current_value" ]] && return 0

  if [[ ! -t 0 ]]; then
    die "Missing generated invoice field: ${variable_name}. Run interactively or provide all generation options."
  fi

  local entered
  if [[ -n "$default_value" ]]; then
    read -r -p "$prompt [$default_value]: " entered
    entered="${entered:-$default_value}"
  else
    while [[ -z "$entered" ]]; do
      read -r -p "$prompt: " entered
    done
  fi
  printf -v "$variable_name" '%s' "$entered"
}

prepare_generated_invoice_fields() {
  [[ "$GENERATE_INVOICE" == "1" ]] || return 0

  log_step "INVOICE GENERATION — Enter the invoice data"
  prompt_value INVOICE_NUMBER "Invoice number"
  prompt_value INVOICE_DATE "Invoice date" "$(date +%F)"
  prompt_value CURRENCY "Currency" "TND"
  prompt_value SELLER_TAX_ID "Seller tax identifier"
  prompt_value SELLER_NAME "Seller legal name"
  prompt_value BUYER_TAX_ID "Buyer tax identifier"
  prompt_value BUYER_NAME "Buyer legal name"
  prompt_value ITEM_DESCRIPTION "Item description"
  prompt_value QUANTITY "Quantity" "1"
  prompt_value UNIT_PRICE "Unit price excluding VAT"
  prompt_value VAT_RATE "VAT rate" "19"

  # Normalize tax identifiers to uppercase and remove common visual separators.
  # Canonical format expected by the API: 7 digits + 3 letters + 3 digits,
  # for example: 1234567RAM000.
  SELLER_TAX_ID="$(printf '%s' "$SELLER_TAX_ID" | tr '[:lower:]' '[:upper:]' | tr -d ' /.-')"
  BUYER_TAX_ID="$(printf '%s' "$BUYER_TAX_ID" | tr '[:lower:]' '[:upper:]' | tr -d ' /.-')"

  [[ "$SELLER_TAX_ID" =~ ^[0-9]{7}[A-Z]{3}[0-9]{3}$ ]] || \
    die "Invalid seller tax identifier: '$SELLER_TAX_ID'. Expected 7 digits + 3 letters + 3 digits, for example 1234567RAM000"
  [[ "$BUYER_TAX_ID" =~ ^[0-9]{7}[A-Z]{3}[0-9]{3}$ ]] || \
    die "Invalid buyer tax identifier: '$BUYER_TAX_ID'. Expected 7 digits + 3 letters + 3 digits, for example 7654321RAM000"

  [[ "$INVOICE_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || die "Invoice date must use YYYY-MM-DD"
  [[ "$QUANTITY" =~ ^[0-9]+([.][0-9]+)?$ ]] || die "Quantity must be numeric"
  [[ "$UNIT_PRICE" =~ ^[0-9]+([.][0-9]+)?$ ]] || die "Unit price must be numeric"
  [[ "$VAT_RATE" =~ ^[0-9]+([.][0-9]+)?$ ]] || die "VAT rate must be numeric"
  [[ "$STAMP_DUTY" =~ ^[0-9]+([.][0-9]+)?$ ]] || die "Stamp duty must be numeric"
}

# ---------------------------------------------------------------------------
# Built-in invoice generator
# ---------------------------------------------------------------------------
generate_invoice_file() {
  local subtotal vat_amount taxes_total total invoice_date_ddmmyy
  subtotal="$(awk -v q="$QUANTITY" -v p="$UNIT_PRICE" 'BEGIN { printf "%.3f", q * p }')"
  vat_amount="$(awk -v s="$subtotal" -v r="$VAT_RATE" 'BEGIN { printf "%.3f", s * r / 100 }')"
  taxes_total="$(awk -v v="$vat_amount" -v t="$STAMP_DUTY" 'BEGIN { printf "%.3f", v + t }')"
  total="$(awk -v s="$subtotal" -v tx="$taxes_total" 'BEGIN { printf "%.3f", s + tx }')"
  invoice_date_ddmmyy="$(date -d "$INVOICE_DATE" +%d%m%y 2>/dev/null)" || die "Could not convert invoice date: $INVOICE_DATE"

  if [[ "$INVOICE_FORMAT" == "json" ]]; then
    INVOICE_FILE="$TMP_DIR/generated-invoice.json"
    jq -n \
      --arg invoiceNumber "$INVOICE_NUMBER" \
      --arg invoiceDate "$invoice_date_ddmmyy" \
      --arg currency "$CURRENCY" \
      --arg sellerTaxId "$SELLER_TAX_ID" --arg sellerName "$SELLER_NAME" \
      --arg sellerAddressDesc "$SELLER_ADDRESS_DESC" --arg sellerStreet "$SELLER_STREET" \
      --arg sellerCity "$SELLER_CITY" --arg sellerPostal "$SELLER_POSTAL_CODE" \
      --arg buyerTaxId "$BUYER_TAX_ID" --arg buyerName "$BUYER_NAME" \
      --arg buyerAddressDesc "$BUYER_ADDRESS_DESC" --arg buyerStreet "$BUYER_STREET" \
      --arg buyerCity "$BUYER_CITY" --arg buyerPostal "$BUYER_POSTAL_CODE" \
      --arg itemId "$ITEM_ID" --arg itemCode "$ITEM_CODE" --arg itemUnit "$ITEM_UNIT" \
      --arg description "$ITEM_DESCRIPTION" --arg paymentCode "$PAYMENT_CONDITION_CODE" \
      --arg quantity "$QUANTITY" --arg vatRate "$VAT_RATE" \
      --arg subtotal "$subtotal" --arg vatAmount "$vat_amount" \
      --arg taxesTotal "$taxes_total" --arg total "$total" \
      '{
        version: "1.8.8",
        controlingAgency: "TTN",
        header: {
          MessageSenderIdentifier: {"@type": "I-01", "#text": $sellerTaxId},
          MessageRecieverIdentifier: {"@type": "I-01", "#text": $buyerTaxId}
        },
        body: {
          bgm: {documentIdentifier: $invoiceNumber, documentTypeCode: "I-11", documentType: "Facture"},
          dtm: {dateText: [{functionCode: "I-31", format: "ddMMyy", value: $invoiceDate}]},
          partnerSection: {partnerDetails: [
            {
              functionCode: "I-62",
              nad: {
                partnerIdentifier: {type: "I-01", value: $sellerTaxId},
                partnerName: {nameType: "Qualification", value: $sellerName},
                partnerAdresses: [{adressDescription: $sellerAddressDesc, street: $sellerStreet, cityName: $sellerCity, postalCode: $sellerPostal, country: {codeList: "ISO_3166-1", value: "TN"}}]
              }, ctaSection: [], rffSection: [], loc: []
            },
            {
              functionCode: "I-61",
              nad: {
                partnerIdentifier: {type: "I-01", value: $buyerTaxId},
                partnerName: {nameType: "Qualification", value: $buyerName},
                partnerAdresses: [{adressDescription: $buyerAddressDesc, street: $buyerStreet, cityName: $buyerCity, postalCode: $buyerPostal, country: {codeList: "ISO_3166-1", value: "TN"}}]
              }, ctaSection: [], rffSection: [], loc: []
            }
          ]},
          linSection: {lin: [{
            itemIdentifier: $itemId,
            linImd: {lang: "fr", itemCode: $itemCode, itemDescription: $description},
            linQty: {quantity: {measurementUnit: $itemUnit, value: $quantity}},
            linTax: {taxTypeName: {code: "I-1602", value: "TVA"}, taxDetails: {taxRate: $vatRate, taxRateBasis: "Percentage"}},
            linMoa: {moaDetails: [{moa: {amountTypeCode: "I-188", currencyCodeList: "ISO_4217", amount: {currencyIdentifier: $currency, value: $subtotal}}}]},
            subLin: []
          }]},
          invoiceTax: {invoiceTaxDetails: [{
            tax: {taxTypeName: {code: "I-1602", value: "TVA"}, taxCategory: "I-1602", taxDetails: {taxRate: $vatRate, taxRateBasis: "Percentage"}},
            amountDetails: [
              {moa: {amountTypeCode: "I-177", currencyCodeList: "ISO_4217", amount: {currencyIdentifier: $currency, value: $subtotal}}},
              {moa: {amountTypeCode: "I-178", currencyCodeList: "ISO_4217", amount: {currencyIdentifier: $currency, value: $vatAmount}}}
            ]
          }]},
          invoiceMoa: {amountDetails: [
            {moa: {amountTypeCode: "I-172", currencyCodeList: "ISO_4217", amount: {currencyIdentifier: $currency, value: $subtotal}}},
            {moa: {amountTypeCode: "I-181", currencyCodeList: "ISO_4217", amount: {currencyIdentifier: $currency, value: $taxesTotal}}},
            {moa: {amountTypeCode: "I-180", currencyCodeList: "ISO_4217", amount: {currencyIdentifier: $currency, value: $total}}},
            {moa: {amountTypeCode: "I-177", currencyCodeList: "ISO_4217", amount: {currencyIdentifier: $currency, value: $subtotal}}},
            {moa: {amountTypeCode: "I-176", currencyCodeList: "ISO_4217", amount: {currencyIdentifier: $currency, value: $subtotal}}}
          ]}
        },
        additionnalDocuments: null,
        refTtnVal: null,
        signatures: []
      }' > "$INVOICE_FILE"
  else
    INVOICE_FILE="$TMP_DIR/generated-invoice.xml"
    cat > "$INVOICE_FILE" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<TEIF controlingAgency="TTN" version="1.8.8">
  <InvoiceHeader>
    <MessageSenderIdentifier type="I-01">$(xml_escape "$SELLER_TAX_ID")</MessageSenderIdentifier>
    <MessageRecieverIdentifier type="I-01">$(xml_escape "$BUYER_TAX_ID")</MessageRecieverIdentifier>
  </InvoiceHeader>
  <InvoiceBody>
    <Bgm><DocumentIdentifier>$(xml_escape "$INVOICE_NUMBER")</DocumentIdentifier><DocumentType code="I-11">Facture</DocumentType></Bgm>
    <Dtm><DateText format="ddMMyy" functionCode="I-31">$invoice_date_ddmmyy</DateText></Dtm>
    <PartnerSection>
      <PartnerDetails functionCode="I-63">
        <Nad>
          <PartnerIdentifier type="I-01">$(xml_escape "$SELLER_TAX_ID")</PartnerIdentifier>
          <PartnerName nameType="Physical">$(xml_escape "$SELLER_NAME")</PartnerName>
          <PartnerAdresses lang="fr"><AdressDescription>$(xml_escape "$SELLER_ADDRESS_DESC")</AdressDescription><Street>$(xml_escape "$SELLER_STREET")</Street><CityName>$(xml_escape "$SELLER_CITY")</CityName><PostalCode>$(xml_escape "$SELLER_POSTAL_CODE")</PostalCode><Country codeList="ISO_3166-1">TN</Country></PartnerAdresses>
        </Nad>
        <CtaSection><Contact functionCode="I-91"><ContactIdentifier>$(xml_escape "$SELLER_PHONE")</ContactIdentifier><ContactName>$(xml_escape "$SELLER_CONTACT_NAME")</ContactName></Contact><Communication><ComMeansType>I-102</ComMeansType><ComAdress>$(xml_escape "$SELLER_PHONE")</ComAdress></Communication></CtaSection>
        <CtaSection><Contact functionCode="I-91"><ContactIdentifier>$(xml_escape "$SELLER_CONTACT_ID")</ContactIdentifier><ContactName>$(xml_escape "$SELLER_CONTACT_NAME")</ContactName></Contact><Communication><ComMeansType>I-101</ComMeansType><ComAdress>$(xml_escape "$SELLER_EMAIL")</ComAdress></Communication></CtaSection>
      </PartnerDetails>
      <PartnerDetails functionCode="I-64"><Nad><PartnerIdentifier type="I-01">$(xml_escape "$BUYER_TAX_ID")</PartnerIdentifier><PartnerName nameType="Physical">$(xml_escape "$BUYER_NAME")</PartnerName><PartnerAdresses lang="fr"><AdressDescription>$(xml_escape "$BUYER_ADDRESS_DESC")</AdressDescription><Street>$(xml_escape "$BUYER_STREET")</Street><CityName>$(xml_escape "$BUYER_CITY")</CityName><PostalCode>$(xml_escape "$BUYER_POSTAL_CODE")</PostalCode><Country codeList="ISO_3166-1">TN</Country></PartnerAdresses></Nad></PartnerDetails>
    </PartnerSection>
    <LinSection><Lin>
      <ItemIdentifier>$(xml_escape "$ITEM_ID")</ItemIdentifier>
      <LinImd lang="fr"><ItemCode>$(xml_escape "$ITEM_CODE")</ItemCode><ItemDescription>$(xml_escape "$ITEM_DESCRIPTION")</ItemDescription></LinImd>
      <LinQty><Quantity measurementUnit="$(xml_escape "$ITEM_UNIT")">$QUANTITY</Quantity></LinQty>
      <LinTax><TaxTypeName code="I-1602">TVA</TaxTypeName><TaxCategory>S</TaxCategory><TaxDetails><TaxRate>$VAT_RATE</TaxRate></TaxDetails></LinTax>
      <LinMoa><MoaDetails><Moa amountTypeCode="I-188" currencyCodeList="ISO_4217"><Amount currencyIdentifier="$(xml_escape "$CURRENCY")">$subtotal</Amount></Moa></MoaDetails><MoaDetails><Moa amountTypeCode="I-171" currencyCodeList="ISO_4217"><Amount currencyIdentifier="$(xml_escape "$CURRENCY")">$subtotal</Amount></Moa></MoaDetails></LinMoa>
    </Lin></LinSection>
    <InvoiceMoa>
      <AmountDetails><Moa amountTypeCode="I-172" currencyCodeList="ISO_4217"><Amount currencyIdentifier="$(xml_escape "$CURRENCY")">$subtotal</Amount></Moa></AmountDetails>
      <AmountDetails><Moa amountTypeCode="I-181" currencyCodeList="ISO_4217"><Amount currencyIdentifier="$(xml_escape "$CURRENCY")">$taxes_total</Amount></Moa></AmountDetails>
      <AmountDetails><Moa amountTypeCode="I-180" currencyCodeList="ISO_4217"><Amount currencyIdentifier="$(xml_escape "$CURRENCY")">$total</Amount></Moa></AmountDetails>
      <AmountDetails><Moa amountTypeCode="I-177" currencyCodeList="ISO_4217"><Amount currencyIdentifier="$(xml_escape "$CURRENCY")">$subtotal</Amount></Moa></AmountDetails>
      <AmountDetails><Moa amountTypeCode="I-176" currencyCodeList="ISO_4217"><Amount currencyIdentifier="$(xml_escape "$CURRENCY")">$subtotal</Amount></Moa></AmountDetails>
    </InvoiceMoa>
    <InvoiceTax>
      <InvoiceTaxDetails><Tax><TaxTypeName code="I-1602">TVA</TaxTypeName><TaxCategory>S</TaxCategory><TaxDetails><TaxRate>$VAT_RATE</TaxRate></TaxDetails></Tax><AmountDetails><Moa amountTypeCode="I-178" currencyCodeList="ISO_4217"><Amount currencyIdentifier="$(xml_escape "$CURRENCY")">$vat_amount</Amount></Moa></AmountDetails></InvoiceTaxDetails>
      <InvoiceTaxDetails><Tax><TaxTypeName code="I-1601">Timbre</TaxTypeName><TaxCategory>O</TaxCategory><TaxDetails><TaxRate>0</TaxRate></TaxDetails></Tax><AmountDetails><Moa amountTypeCode="I-178" currencyCodeList="ISO_4217"><Amount currencyIdentifier="$(xml_escape "$CURRENCY")">$STAMP_DUTY</Amount></Moa></AmountDetails></InvoiceTaxDetails>
    </InvoiceTax>
  </InvoiceBody>
</TEIF>
EOF
  fi

  log_ok "Generated TEIF invoice payload: $INVOICE_FILE"
  if [[ "$DEBUG" == "1" ]]; then
    printf '\n%s%s[DEBUG GENERATED INVOICE]%s %s\n' "$C_BOLD" "$C_MAGENTA" "$C_RESET" "$INVOICE_FILE" >&2
    if [[ "$INVOICE_FORMAT" == "json" ]]; then
      jq . "$INVOICE_FILE" >&2
    else
      cat "$INVOICE_FILE" >&2
    fi
    printf '\n' >&2
  fi
}

xml_escape() {
  local value="$1"
  value="${value//&/&amp;}"
  value="${value//</&lt;}"
  value="${value//>/&gt;}"
  value="${value//\"/&quot;}"
  value="${value//\'/&apos;}"
  printf '%s' "$value"
}

# ---------------------------------------------------------------------------
# HTTP helpers
# ---------------------------------------------------------------------------

# do_request METHOD PATH [BODY_FILE] [CONTENT_TYPE]
# Sets HTTP_CODE and RESPONSE_FILE (response body). Uses ACCESS_TOKEN
# as an Authorization Bearer token when available.
do_request() {
  local method="$1" path="$2" body_file="${3:-}" ctype="${4:-application/json}"
  local url="${BASE_URL}${path}"
  RESPONSE_FILE="$TMP_DIR/response.json"
  local -a args=("${CURL_OPTS[@]}" -X "$method" "$url" -o "$RESPONSE_FILE" -w '%{http_code}')
  [[ -n "${ACCESS_TOKEN:-}" ]] && args+=(-H "Authorization: Bearer ${ACCESS_TOKEN}")
  if [[ -n "$body_file" ]]; then
    args+=(-H "Content-Type: ${ctype}")
    if [[ "$GENERATE_INVOICE" == "1" && "$path" == *"/invoices/submit"* ]]; then
      args+=(-H "X-Allow-Partner-Upsert: true")
    fi
    if [[ "$ctype" == application/xml* || "$ctype" == text/xml* ]]; then
      args+=(--data-binary "@${body_file}")
    else
      args+=(--data "@${body_file}")
    fi
  fi
  debug_curl_command "${args[@]}"
  HTTP_CODE="$(curl "${args[@]}")" || die "Network error while calling ${url}"
  debug_http_response "$HTTP_CODE" "$RESPONSE_FILE" "$method $path"
}

# download_file PATH OUTPUT_PATH
download_file() {
  local path="$1" out="$2"
  local url="${BASE_URL}${path}"
  local -a args=("${CURL_OPTS[@]}" -X GET "$url" -H "Authorization: Bearer ${ACCESS_TOKEN}" -o "$out" -w '%{http_code}')
  debug_curl_command "${args[@]}"
  HTTP_CODE="$(curl "${args[@]}")" || die "Network error while calling ${url}"
  debug_download_response "$HTTP_CODE" "$out"
}

# request_token [ORG_ID]
# Prints the access_token to stdout. Includes a simple retry for HTTP 429.
request_token() {
  local org_id="${1:-}"
  local url="${BASE_URL}/oauth/token"
  local resp="$TMP_DIR/token.json"
  local -a args=("${CURL_OPTS[@]}" -X POST "$url" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    --data-urlencode "grant_type=client_credentials" \
    --data-urlencode "client_id=${CLIENT_ID}" \
    --data-urlencode "client_secret=${CLIENT_SECRET}" \
    -o "$resp" -w '%{http_code}')
  [[ -n "$org_id" ]] && args+=(--data-urlencode "org_id=${org_id}")

  local code attempt=1 max_attempts=3
  while true; do
    debug_curl_command "${args[@]}"
    code="$(curl "${args[@]}")" || die "Network error while calling ${url}"
    if [[ "" == "1" ]]; then
      printf '%s%s[DEBUG RESPONSE]%s POST /oauth/token — HTTP %s (token body redacted)%s\n' \
        "" "" "" "" "" >&2
    fi
    if [[ "$code" == "200" ]]; then
      break
    fi
    if [[ "$code" == "429" && "$attempt" -lt "$max_attempts" ]]; then
      log_warn "POST /oauth/token was rate-limited (HTTP 429) — retrying in 5 seconds ($attempt/$max_attempts)"
      sleep 5
      attempt=$((attempt + 1))
      continue
    fi
    log_error "POST /oauth/token failed — HTTP $code"
    jq . "$resp" >&2 2>/dev/null || cat "$resp" >&2
    return 1
  done

  local token
  token="$(jq -r '.access_token // empty' "$resp")"
  [[ -n "$token" ]] || { log_error "HTTP 200 response did not contain an access_token"; cat "$resp" >&2; return 1; }
  printf '%s' "$token"
}

# ---------------------------------------------------------------------------
# Workflow steps
# ---------------------------------------------------------------------------

select_partner_org() {
  do_request GET "/api/partner/authorized-orgs"
  if [[ "$HTTP_CODE" != "200" ]]; then
    jq . "$RESPONSE_FILE" >&2 2>/dev/null || sed -n '1,120p' "$RESPONSE_FILE" >&2
    die "GET /api/partner/authorized-orgs failed (HTTP $HTTP_CODE)"
  fi

  local total
  total="$(jq -r '.total // (.authorizedOrgs | length) // 0' "$RESPONSE_FILE")"
  [[ "$total" -gt 0 ]] || die "No organization currently authorizes this partner application."

  if [[ -n "$REQUESTED_ORG_ID" ]]; then
    SELECTED_ORG_ID="$(jq -r --arg id "$REQUESTED_ORG_ID" '.authorizedOrgs[] | select(.orgId == $id) | .orgId' "$RESPONSE_FILE" | head -n 1)"
    [[ -n "$SELECTED_ORG_ID" ]] || die "Organization '$REQUESTED_ORG_ID' is not authorized for this partner application"
  elif [[ -n "$REQUESTED_ORG_TAX_ID" ]]; then
    local normalized_tax_id
    normalized_tax_id="$(printf '%s' "$REQUESTED_ORG_TAX_ID" | tr '[:lower:]' '[:upper:]' | tr -d ' /.-')"
    SELECTED_ORG_ID="$(jq -r --arg tax "$normalized_tax_id" '.authorizedOrgs[] | select((.taxIdentifier // "" | ascii_upcase | gsub("[ /.-]"; "")) == $tax) | .orgId' "$RESPONSE_FILE" | head -n 1)"
    [[ -n "$SELECTED_ORG_ID" ]] || die "No authorized organization matches tax identifier '$REQUESTED_ORG_TAX_ID'"
  elif [[ "$INTERACTIVE" == "1" ]]; then
    log_info "Organizations authorizing this partner application:"
    local i=1
    while IFS=$'\t' read -r org_id org_name tax_id; do
      printf '  [%d] %s (tax identifier %s) — %s\n' "$i" "$org_name" "${tax_id:-not set}" "$org_id"
      i=$((i + 1))
    done < <(jq -r '.authorizedOrgs[] | [.orgId, .orgName, (.taxIdentifier // "")] | @tsv' "$RESPONSE_FILE")

    local choice
    read -r -p "Select an organization [1-${total}]: " choice
    [[ "$choice" =~ ^[0-9]+$ && "$choice" -ge 1 && "$choice" -le "$total" ]] || die "Invalid selection: $choice"
    SELECTED_ORG_ID="$(jq -r ".authorizedOrgs[$((choice - 1))].orgId" "$RESPONSE_FILE")"
  elif [[ "$total" -eq 1 ]]; then
    SELECTED_ORG_ID="$(jq -r '.authorizedOrgs[0].orgId' "$RESPONSE_FILE")"
    log_info "Automatically selected the only authorized organization: $SELECTED_ORG_ID"
  else
    die "Multiple organizations are authorized. Use --org-id, --org-tax-id, or --interactive; refusing to select a customer implicitly."
  fi
}

authenticate() {
  case "$MODE" in
    organization)
      log_step "STEP 1/6 — Authentication (Organization API, client_credentials)"
      ACCESS_TOKEN="$(request_token)" || die "Authentication failed"
      log_ok "Token obtained"
      ;;
    partner)
      log_step "STEP 1/6 — Authentication (Partner API): app-only token"
      local app_token
      app_token="$(request_token)" || die "App-only authentication failed"
      log_ok "App-only token obtained"

      ACCESS_TOKEN="$app_token"
      select_partner_org

      log_info "Requesting an organization-scoped token for the selected organization ($SELECTED_ORG_ID)"
      ACCESS_TOKEN="$(request_token "$SELECTED_ORG_ID")" || die "Organization-scoped authentication failed"
      log_ok "Organization-scoped token obtained"
      ;;
  esac
}

fetch_organization_identity() {
  log_step "ORGANIZATION — Retrieving the invoicing identity"
  do_request GET "${API_PREFIX}/organization"
  if [[ "$HTTP_CODE" != "200" ]]; then
    jq . "$RESPONSE_FILE" >&2 2>/dev/null || sed -n '1,120p' "$RESPONSE_FILE" >&2
    die "GET ${API_PREFIX}/organization failed (HTTP $HTTP_CODE)"
  fi

  ORGANIZATION_ID="$(jq -r '.orgId // empty' "$RESPONSE_FILE")"
  ORGANIZATION_NAME="$(jq -r '.orgName // empty' "$RESPONSE_FILE")"
  ORGANIZATION_TAX_ID="$(jq -r '.taxIdentifier // empty' "$RESPONSE_FILE" | tr '[:lower:]' '[:upper:]' | tr -d ' /.-')"
  [[ -n "$ORGANIZATION_ID" ]] || die "Organization response does not contain orgId"
  [[ -n "$ORGANIZATION_TAX_ID" ]] || die "Organization '$ORGANIZATION_ID' has no taxIdentifier; configure it before invoicing"

  if [[ -n "$SELLER_TAX_ID" ]]; then
    local requested_tax_id
    requested_tax_id="$(printf '%s' "$SELLER_TAX_ID" | tr '[:lower:]' '[:upper:]' | tr -d ' /.-')"
    [[ "$requested_tax_id" == "$ORGANIZATION_TAX_ID" ]] || die "--seller-tax-id does not match the authenticated organization taxIdentifier"
  else
    SELLER_TAX_ID="$ORGANIZATION_TAX_ID"
  fi
  [[ -n "$SELLER_NAME" ]] || SELLER_NAME="$ORGANIZATION_NAME"
  log_ok "Organization resolved: ${ORGANIZATION_NAME:-$ORGANIZATION_ID} (tax identifier $ORGANIZATION_TAX_ID)"
}

validate_invoice_sender() {
  [[ "$GENERATE_INVOICE" == "0" ]] || return 0
  local invoice_tax_id=""
  if [[ "$INVOICE_FORMAT" == "json" ]]; then
    jq empty "$INVOICE_FILE" >/dev/null 2>&1 || die "Invoice file is not valid JSON: $INVOICE_FILE"
    invoice_tax_id="$(jq -r '.header.MessageSenderIdentifier["#text"] // .header.messageSenderIdentifier.value // empty' "$INVOICE_FILE")"
  else
    invoice_tax_id="$(sed -n 's:.*<MessageSenderIdentifier[^>]*>[[:space:]]*\([^<]*\)[[:space:]]*</MessageSenderIdentifier>.*:\1:p' "$INVOICE_FILE" | head -n 1)"
  fi
  invoice_tax_id="$(printf '%s' "$invoice_tax_id" | tr '[:lower:]' '[:upper:]' | tr -d ' /.-')"
  [[ -n "$invoice_tax_id" ]] || die "Could not extract MessageSenderIdentifier from $INVOICE_FILE"
  [[ "$invoice_tax_id" == "$ORGANIZATION_TAX_ID" ]] || die "Invoice MessageSenderIdentifier '$invoice_tax_id' does not match authenticated organization taxIdentifier '$ORGANIZATION_TAX_ID'"
  log_ok "Invoice sender matches the authenticated organization"
}

submit_invoice() {
  log_step "STEP 2/6 — Invoice submission ($INVOICE_FORMAT format)"
  local path ctype
  if [[ "$INVOICE_FORMAT" == "xml" ]]; then
    path="${API_PREFIX}/invoices/submit-xml"
    ctype="application/xml"
  else
    path="${API_PREFIX}/invoices/submit"
    ctype="application/json"
  fi

  do_request POST "$path" "$INVOICE_FILE" "$ctype"
  if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" ]]; then
    jq . "$RESPONSE_FILE" >&2 2>/dev/null || cat "$RESPONSE_FILE" >&2
    die "Invoice submission failed (HTTP $HTTP_CODE)"
  fi

  INVOICE_ID="$(jq -r '.id // empty' "$RESPONSE_FILE")"
  [[ -n "$INVOICE_ID" ]] || die "Could not extract the invoice ID from the response"
  local status
  status="$(jq -r '.status // empty' "$RESPONSE_FILE")"
  log_ok "Invoice submitted: id=$INVOICE_ID status=$status"
}

trigger_sign_and_send() {
  log_step "STEP 3/6 — Triggering SEAL signature and sending to TTN"
  local body_file="$TMP_DIR/sign-and-send.json"
  jq -n --arg st "SEAL" '{signatureType: $st}' >"$body_file"

  do_request POST "${API_PREFIX}/invoices/${INVOICE_ID}/sign-and-send" "$body_file" "application/json"
  if [[ "$HTTP_CODE" != "200" ]]; then
    jq . "$RESPONSE_FILE" >&2 2>/dev/null || cat "$RESPONSE_FILE" >&2
    die "sign-and-send failed (HTTP $HTTP_CODE)"
  fi

  JOB_ID="$(jq -r '.jobId // empty' "$RESPONSE_FILE")"
  [[ -n "$JOB_ID" ]] || die "Could not extract jobId from the sign-and-send response"
  log_ok "Job created: jobId=$JOB_ID"
}

poll_job() {
  log_step "STEP 4/6 — Polling the job until TTN acceptance (interval ${POLL_INTERVAL}s, timeout ${POLL_TIMEOUT}s)"
  local elapsed=0 status="" progress="" step=""
  while (( elapsed < POLL_TIMEOUT )); do
    do_request GET "${API_PREFIX}/jobs/${JOB_ID}"
    if [[ "$HTTP_CODE" != "200" ]]; then
      jq . "$RESPONSE_FILE" >&2 2>/dev/null || cat "$RESPONSE_FILE" >&2
      die "GET /jobs/${JOB_ID} failed (HTTP $HTTP_CODE)"
    fi
    status="$(jq -r '.status // empty' "$RESPONSE_FILE")"
    progress="$(jq -r '.progress // empty' "$RESPONSE_FILE")"
    step="$(jq -r '.currentStep // empty' "$RESPONSE_FILE")"
    log_info "  [$(date '+%H:%M:%S')] status=$status progress=${progress:-?}% step=${step:-?}"

    case "$status" in
      COMPLETED)
        log_ok "Job completed successfully"
        return 0
        ;;
      FAILED|TTN_REJECTED|VALIDATION_FAILED|CANCELLED)
        local err
        err="$(jq -r '.errorMessage // empty' "$RESPONSE_FILE")"
        die "Job failed (status=$status)${err:+: $err}"
        ;;
      WAITING_DIGIGO_AUTH)
        die "Job is waiting for DigiGO authentication, which is not supported by this script. Use --signature-type seal."
        ;;
    esac

    sleep "$POLL_INTERVAL"
    elapsed=$((elapsed + POLL_INTERVAL))
  done
  die "Polling timeout exceeded (${POLL_TIMEOUT}s) — last observed status: ${status:-unknown}"
}

fetch_artifacts() {
  log_step "STEP 5/6 — Retrieving the final invoice and signed XML"
  do_request GET "${API_PREFIX}/invoices/${INVOICE_ID}"
  if [[ "$HTTP_CODE" != "200" ]]; then
    jq . "$RESPONSE_FILE" >&2 2>/dev/null || cat "$RESPONSE_FILE" >&2
    die "GET /invoices/${INVOICE_ID} failed (HTTP $HTTP_CODE)"
  fi

  FINAL_STATUS="$(jq -r '.invoice.status // empty' "$RESPONSE_FILE")"
  if [[ "$FINAL_STATUS" == "ACCEPTED_TTN" ]]; then
    log_ok "Invoice accepted by TTN"
  else
    log_warn "Final invoice status: ${FINAL_STATUS:-unknown} (expected ACCEPTED_TTN)"
  fi

  mkdir -p "$OUTPUT_DIR"
  XML_OUT="${OUTPUT_DIR}/${INVOICE_ID}.xml"
  jq -r '.teifXml // empty' "$RESPONSE_FILE" >"$XML_OUT"
  [[ -s "$XML_OUT" ]] || die "The teifXml field is empty in the response; the signed XML cannot be saved"
  log_ok "Signed XML saved: $XML_OUT"

  log_step "STEP 6/6 — Downloading the PDF"
  PDF_OUT="${OUTPUT_DIR}/${INVOICE_ID}.pdf"
  download_file "${API_PREFIX}/invoices/${INVOICE_ID}/pdf" "$PDF_OUT"
  [[ "$HTTP_CODE" == "200" ]] || die "PDF download failed (HTTP $HTTP_CODE)"
  log_ok "PDF saved: $PDF_OUT"
}

# ---------------------------------------------------------------------------
# main
# ---------------------------------------------------------------------------
main() {
  parse_args "$@"
  validate_args
  need_cmd curl
  need_cmd jq
  need_cmd awk
  build_curl_opts

  TMP_DIR="$(mktemp -d)"
  trap 'rm -rf "$TMP_DIR"' EXIT

  case "$MODE" in
    organization) API_PREFIX="/api/v2" ;;
    partner) API_PREFIX="/api/core-proxy" ;;
  esac

  authenticate
  fetch_organization_identity

  if [[ "$GENERATE_INVOICE" == "1" ]]; then
    prepare_generated_invoice_fields
    generate_invoice_file
  else
    validate_invoice_sender
  fi

  submit_invoice
  trigger_sign_and_send
  poll_job
  fetch_artifacts

  echo
  log_ok "Complete workflow finished — invoiceId=$INVOICE_ID status=${FINAL_STATUS:-unknown}"
  log_info "  XML : $XML_OUT"
  log_info "  PDF : $PDF_OUT"
}

main "$@"
