#!/usr/bin/env bash
set -u

# Digital SDP / SPay Legacy API interactive server-side test utility
# TEMPORARY MIGRATION-PERIOD TESTING AID ONLY.
# This script is intended only to facilitate integration testing, troubleshooting,
# and controlled validation during the migration period. It is NOT an official
# integration reference, API specification, or replacement for Huawei documentation.
# For implementation and production integration, always rely on the latest official
# Huawei Digital SDP API documentation. If there is any difference, the official
# Huawei API documentation takes precedence.
# Requires: curl, jq, openssl, base64, fold, sed, awk

C_RESET='\033[0m'; C_BOLD='\033[1m'; C_GREEN='\033[32m'; C_RED='\033[31m'; C_YELLOW='\033[33m'; C_CYAN='\033[36m'

say() { printf "%b\n" "$*"; }
section() { say "\n${C_BOLD}${C_CYAN}==== $* ====${C_RESET}"; }
ok() { say "${C_GREEN}[OK]${C_RESET} $*"; }
warn() { say "${C_YELLOW}[WARN]${C_RESET} $*"; }
err() { say "${C_RED}[ERROR]${C_RESET} $*"; }
mask() { local s="${1:-}"; local n=${#s}; if (( n <= 8 )); then printf '********'; else printf '%s...%s' "${s:0:4}" "${s:n-4:4}"; fi; }

for cmd in curl jq openssl base64 fold sed awk; do
  command -v "$cmd" >/dev/null 2>&1 || { err "Required command '$cmd' is not installed."; exit 2; }
done

TMP_DIR="$(mktemp -d -t sdp-test.XXXXXX)"
trap 'rm -rf "$TMP_DIR"' EXIT
REPORT="$TMP_DIR/report.log"
TOKEN=""
HTTP_CODE=""
BODY=""
HEADERS=""
LAST_CURL_RC=0

log() { printf '%s\n' "$*" >> "$REPORT"; }

recommend_code() {
  local code="${1:-unknown}"
  case "$code" in
    0) echo "Internal SPay API error. Retry once; if persistent, share timestamp, endpoint, HTTP status and response with SDP/Huawei support." ;;
    1) echo "Success." ;;
    100) echo "Public-key retrieval failed. Re-test GetPublicKey, providerKey mapping, DNS/TLS/network connectivity." ;;
    101) echo "Check HTTP method, URL path, Content-Type and JSON structure." ;;
    102) echo "Token is invalid/expired. Run Login again and use the newly returned token." ;;
    103) echo "Token is missing. Ensure the HTTP header name is exactly 'token' and contains the Login token." ;;
    104) echo "Check mandatory request parameters, names, JSON types and values." ;;
    105) echo "PIN is invalid. Re-enter the 6-digit PIN delivered for this InitPay request." ;;
    106) echo "Validate MSISDN format/value and that the test number is supported." ;;
    107) echo "The service does not belong to this logged-in provider. Verify serviceCode-to-provider assignment." ;;
    108) echo "Provider data lookup failed. Verify SP/provider provisioning with DSDP/Huawei." ;;
    109) echo "Credentials failed. Verify username and original password, and confirm RSA-2048 PKCS#1 v1.5 encryption using the latest public key." ;;
    110) echo "Payment request was created by another provider. Verify provider ownership and requestId." ;;
    111) echo "Request is already paid or balance is insufficient. Check request state and subscriber balance." ;;
    112) echo "providerKey is not assigned to the provider. Verify provisioning/mapping on DSDP." ;;
    113) echo "A pending payment request already exists. Complete/cancel the pending flow before creating another." ;;
    114) echo "Unsubscribe failed. Check subscription state, service ownership and DSDP logs." ;;
    115) echo "Service was created by another provider. Verify serviceCode/provider mapping." ;;
    116) echo "MSISDN is already unsubscribed. This can be acceptable when validating cleanup." ;;
    117) echo "MSISDN is not subscribed to the service. For post-unsubscribe verification, this is the expected state." ;;
    118) echo "Subscriber is not active. Verify subscription lifecycle/status." ;;
    120|500) echo "Diameter/backend error. Capture full response, timestamp and correlation context; escalate to core/charging/SDP support." ;;
    121) echo "MSISDN is already registered for this service. Use CheckSubscription or unsubscribe before re-testing subscription." ;;
    122) echo "Service is disabled. Ask service/SDP administrator to verify service status." ;;
    123) echo "Link/API is not authorized. Verify access policy, source IP/network and endpoint authorization." ;;
    124) echo "No services found for the MSISDN. Verify serviceCode and current subscription state." ;;
    *) echo "Unknown/undocumented code. Capture complete HTTP request/response and server timestamp for escalation." ;;
  esac
}

show_response_analysis() {
  local name="$1"
  local code status msg
  code="$(printf '%s' "$BODY" | jq -r '.responseCode // empty' 2>/dev/null || true)"
  status="$(printf '%s' "$BODY" | jq -r '.status // empty' 2>/dev/null || true)"
  msg="$(printf '%s' "$BODY" | jq -r '.responseMessage // empty' 2>/dev/null || true)"
  say "${C_BOLD}Analysis:${C_RESET} API=$name | HTTP=$HTTP_CODE | status=${status:-N/A} | responseCode=${code:-N/A} | message=${msg:-N/A}"
  if [[ "$HTTP_CODE" =~ ^2 ]]; then :; else
    err "Non-2xx HTTP response. Check URL, TLS, proxy/firewall, routing, web server and API availability."
  fi
  if [[ -n "$code" ]]; then
    say "Recommendation: $(recommend_code "$code")"
  elif ! printf '%s' "$BODY" | jq -e . >/dev/null 2>&1; then
    warn "Response is not valid JSON. Review HTTP headers/body; this can indicate proxy, WAF, TLS termination, 404/5xx HTML, or upstream failure."
  fi
}

api_call() {
  local name="$1" method="$2" url="$3" json="$4" token="${5:-}"
  local hdr="$TMP_DIR/${name// /_}.headers" body="$TMP_DIR/${name// /_}.body"
  local -a args
  args=( -sS --connect-timeout 15 --max-time 70 -X "$method" "$url" -H 'Accept: application/json' -H 'Content-Type: application/json' -D "$hdr" -o "$body" -w '%{http_code}' --data "$json" )
  [[ -n "$token" ]] && args+=( -H "token: $token" )

  section "$name - Request"
  say "URL: $url"
  say "Method: $method"
  say "Headers:"
  say "  Accept: application/json"
  say "  Content-Type: application/json"
  [[ -n "$token" ]] && say "  token: $(mask "$token")"
  say "Body:"
  printf '%s' "$json" | jq . 2>/dev/null || printf '%s\n' "$json"

  log "==== $name REQUEST ===="
  log "URL: $url"
  log "Method: $method"
  log "Headers: Accept=application/json; Content-Type=application/json; token=$(mask "$token")"
  log "Body: $json"

  HTTP_CODE="$(curl "${args[@]}")"
  LAST_CURL_RC=$?
  HEADERS="$(cat "$hdr" 2>/dev/null || true)"
  BODY="$(cat "$body" 2>/dev/null || true)"

  section "$name - Response"
  say "curl exit code: $LAST_CURL_RC"
  say "HTTP status: ${HTTP_CODE:-N/A}"
  say "Response headers:"
  printf '%s\n' "$HEADERS"
  say "Response body:"
  if printf '%s' "$BODY" | jq -e . >/dev/null 2>&1; then printf '%s' "$BODY" | jq .; else printf '%s\n' "$BODY"; fi

  log "==== $name RESPONSE ===="
  log "curl_rc=$LAST_CURL_RC HTTP=$HTTP_CODE"
  log "$HEADERS"
  if [[ "$name" == "Login" ]] && printf '%s' "$BODY" | jq -e . >/dev/null 2>&1; then
    log "$(printf '%s' "$BODY" | jq -c 'if .token then .token="<MASKED_TOKEN>" else . end')"
  else
    log "$BODY"
  fi

  if (( LAST_CURL_RC != 0 )); then
    err "curl failed before a normal API response was completed."
    case "$LAST_CURL_RC" in
      6) say "Recommendation: DNS resolution failed. Check resolv.conf/DNS or host mapping for the API domain." ;;
      7) say "Recommendation: TCP connection failed. Check IP/port reachability, firewall, route, listener and NAT." ;;
      28) say "Recommendation: Request timed out. Check network path, firewall, reverse proxy and backend responsiveness." ;;
      35|60) say "Recommendation: TLS/certificate problem. Test with 'curl -v' and validate server certificate chain, hostname/SNI and CA trust." ;;
      *) say "Recommendation: Run curl -v against the same URL and capture network/TLS details." ;;
    esac
  fi
  show_response_analysis "$name"
}

section "Digital SDP / SPay API Interactive Test"
say "This utility supports two subscription test modes:"
section "Important Usage Notice"
say "${C_BOLD}${C_YELLOW}TEMPORARY MIGRATION-PERIOD TESTING AID ONLY${C_RESET}"
say "This script is provided only to facilitate integration testing, troubleshooting, and validation during migration."
say "It is NOT an official integration reference and must NOT be used as a replacement for Huawei API documentation."
say "For implementation and production integration, rely only on the latest official Huawei Digital SDP API documents."
say "If this script differs from the official Huawei documentation, the Huawei documentation takes precedence."

say "  1) Subscribe via API: GetPublicKey -> Login -> InitPay -> Payment -> CheckSubscription -> UnSubscribe -> final CheckSubscription."
say "  2) Subscribe via Landing Page: GetPublicKey -> Login -> wait for manual Landing Page subscription -> CheckSubscription -> UnSubscribe -> final CheckSubscription."
say "Plaintext password is never printed or written to the report. The token is masked in displayed headers/report."

section "Select Test Mode"
say "1) Subscribe via API (InitPay + Payment)"
say "2) Subscribe via Landing Page (skip InitPay + Payment)"
while true; do
  read -r -p "Select option [1/2]: " TEST_MODE
  case "$TEST_MODE" in
    1) TEST_MODE_NAME="API Subscription"; break ;;
    2) TEST_MODE_NAME="Landing Page Subscription"; break ;;
    *) warn "Please enter 1 or 2." ;;
  esac
done

read -r -p "Base URL [https://digital.sudani.sd]: " BASE_URL
BASE_URL="${BASE_URL:-https://digital.sudani.sd}"
BASE_URL="${BASE_URL%/}"
read -r -p "Provider Key (providerKey): " PROVIDER_KEY
read -r -p "Username / Login: " USERNAME
read -r -s -p "Password: " PASSWORD; printf '\n'
read -r -p "Service Code (serviceCode): " SERVICE_CODE
read -r -p "Test MSISDN: " MSISDN

if [[ -z "$PROVIDER_KEY" || -z "$USERNAME" || -z "$PASSWORD" || -z "$SERVICE_CODE" || -z "$MSISDN" ]]; then
  err "All inputs are required."; exit 2
fi

section "Input Summary"
say "Test mode: $TEST_MODE_NAME"
say "Base URL: $BASE_URL"
say "providerKey: $PROVIDER_KEY"
say "login: $USERNAME"
say "password: ******** (hidden)"
say "serviceCode: $SERVICE_CODE"
say "MSISDN: $MSISDN"

# 1) GetPublicKey
GETKEY_JSON="$(jq -nc --arg providerKey "$PROVIDER_KEY" '{providerKey:$providerKey}')"
api_call "GetPublicKey" POST "$BASE_URL/SPayAPI/Service/GetPublicKey" "$GETKEY_JSON"
PUBLIC_KEY="$(printf '%s' "$BODY" | jq -r '.publicKey // empty' 2>/dev/null || true)"
GETKEY_CODE="$(printf '%s' "$BODY" | jq -r '.responseCode // empty' 2>/dev/null || true)"
if [[ "$GETKEY_CODE" != "1" || -z "$PUBLIC_KEY" ]]; then
  err "Cannot continue: a valid publicKey was not returned."
  cp "$REPORT" ./sdp_spay_test_report_$(date +%Y%m%d_%H%M%S).log 2>/dev/null || true
  exit 1
fi
ok "Public key received (${#PUBLIC_KEY} Base64 characters)."

# Build PEM from Base64 DER SubjectPublicKeyInfo and encrypt password using RSA PKCS#1 v1.5.
PUB_PEM="$TMP_DIR/public_key.pem"
{
  echo '-----BEGIN PUBLIC KEY-----'
  printf '%s' "$PUBLIC_KEY" | tr -d '\r\n ' | fold -w 64; echo
  echo '-----END PUBLIC KEY-----'
} > "$PUB_PEM"

if ! openssl pkey -pubin -in "$PUB_PEM" -noout >/dev/null 2>&1; then
  err "Returned publicKey could not be parsed by OpenSSL as an X.509 public key."
  say "Recommendation: confirm the API returned the complete Base64 publicKey without truncation or whitespace corruption."
  exit 1
fi

ENC_PASSWORD="$(printf '%s' "$PASSWORD" | openssl pkeyutl -encrypt -pubin -inkey "$PUB_PEM" -pkeyopt rsa_padding_mode:pkcs1 2>"$TMP_DIR/openssl.err" | base64 | tr -d '\r\n')"
if [[ -z "$ENC_PASSWORD" ]]; then
  err "Password encryption failed."; cat "$TMP_DIR/openssl.err"; exit 1
fi
ok "Password encrypted using RSA + PKCS#1 v1.5 and Base64 encoded."

# 2) Login
LOGIN_JSON="$(jq -nc --arg login "$USERNAME" --arg password "$ENC_PASSWORD" '{login:$login,password:$password}')"
api_call "Login" POST "$BASE_URL/SPayAPI/Service/Login/" "$LOGIN_JSON"
TOKEN="$(printf '%s' "$BODY" | jq -r '.token // empty' 2>/dev/null || true)"
LOGIN_CODE="$(printf '%s' "$BODY" | jq -r '.responseCode // empty' 2>/dev/null || true)"
if [[ "$LOGIN_CODE" != "1" || -z "$TOKEN" ]]; then
  err "Cannot continue: Login did not return a valid token."
  say "Encryption check: RSA-2048 / PKCS#1 v1.5 / Base64 is required by the supplied GetPublicKey&Login document."
  exit 1
fi
ok "Login successful. Token: $(mask "$TOKEN")"
EXPIRE_DATE="$(printf '%s' "$BODY" | jq -r '.expireDate // empty')"
[[ -n "$EXPIRE_DATE" ]] && say "Token expiry (server local time): $EXPIRE_DATE"

# Shared request for CheckSubscription and UnSubscribe.
SUB_JSON="$(jq -nc --arg msisdn "$MSISDN" --arg serviceCode "$SERVICE_CODE" '{msisdn:$msisdn,serviceCode:$serviceCode}')"

if [[ "$TEST_MODE" == "1" ]]; then
  # 3A) Subscribe via API: InitPay
  api_call "InitPay" POST "$BASE_URL/SPayAPI/Service/InitPay" "$SUB_JSON" "$TOKEN"
  INIT_CODE="$(printf '%s' "$BODY" | jq -r '.responseCode // empty' 2>/dev/null || true)"
  REQUEST_ID="$(printf '%s' "$BODY" | jq -r '.requestId // empty' 2>/dev/null || true)"
  if [[ "$INIT_CODE" != "1" || -z "$REQUEST_ID" ]]; then
    err "Cannot continue to Payment: InitPay did not return requestId."
    exit 1
  fi
  ok "InitPay created requestId=$REQUEST_ID. The subscriber should receive the PIN by SMS."

  # 4A) Payment
  read -r -p "Enter the 6-digit PIN received by the subscriber: " PIN
  if [[ ! "$PIN" =~ ^[0-9]{6}$ ]]; then warn "PIN is expected to be exactly 6 digits according to the API document."; fi
  PAY_JSON="$(jq -nc --arg pin "$PIN" --argjson requestId "$REQUEST_ID" '{pin:$pin,requestId:$requestId}')"
  api_call "Payment" POST "$BASE_URL/SPayAPI/Service/Payment/" "$PAY_JSON" "$TOKEN"
  PAY_CODE="$(printf '%s' "$BODY" | jq -r '.responseCode // empty' 2>/dev/null || true)"
  if [[ "$PAY_CODE" == "1" ]]; then
    ok "Payment/subscription request completed successfully."
  else
    warn "Payment did not return responseCode 1. Review the response before proceeding."
  fi

  section "Subscription Verification Gate"
  say "The API subscription flow has completed."
  read -r -p "Run CheckSubscription now? [Y/n]: " CONFIRM_CHECK
  if [[ "$CONFIRM_CHECK" =~ ^[Nn]$ ]]; then
    warn "CheckSubscription was skipped by user."
  else
    api_call "CheckSubscription (after API subscribe)" POST "$BASE_URL/SPayAPI/Service/CheckSubscription" "$SUB_JSON" "$TOKEN"
    CHECK1_CODE="$(printf '%s' "$BODY" | jq -r '.responseCode // empty' 2>/dev/null || true)"
    if [[ "$CHECK1_CODE" == "1" ]]; then ok "Subscription is active according to CheckSubscription."; else warn "Subscription is not confirmed active. Review the response and recommendation above."; fi
  fi
else
  # 3B) Landing Page flow: skip InitPay and Payment completely.
  section "Landing Page Subscription"
  say "InitPay and Payment are skipped in this mode."
  say "Complete the subscription manually through the Landing Page using the same MSISDN and serviceCode shown above."
  say "When the Landing Page subscription is finished, return to this terminal."

  while true; do
    read -r -p "Have you completed the subscription via the Landing Page? [y/N]: " LP_DONE
    if [[ "$LP_DONE" =~ ^[Yy]$ ]]; then
      ok "Landing Page subscription confirmed by tester. Continuing with CheckSubscription."
      break
    fi
    read -r -p "Subscription is not confirmed. Wait and ask again? [Y/n]: " LP_WAIT
    if [[ "$LP_WAIT" =~ ^[Nn]$ ]]; then
      warn "Test stopped before CheckSubscription because Landing Page subscription was not confirmed."
      OUT_REPORT="./sdp_spay_test_report_$(date +%Y%m%d_%H%M%S).log"
      cp "$REPORT" "$OUT_REPORT" 2>/dev/null || true
      say "Diagnostic report saved to: $OUT_REPORT"
      exit 0
    fi
  done

  api_call "CheckSubscription (after Landing Page subscribe)" POST "$BASE_URL/SPayAPI/Service/CheckSubscription" "$SUB_JSON" "$TOKEN"
  CHECK1_CODE="$(printf '%s' "$BODY" | jq -r '.responseCode // empty' 2>/dev/null || true)"
  if [[ "$CHECK1_CODE" == "1" ]]; then
    ok "Subscription is active according to CheckSubscription."
  else
    warn "Landing Page subscription is not confirmed active by CheckSubscription. Review the full response and recommendation above."
  fi
fi

# Unsubscribe/delete gate for both modes.
section "Unsubscribe / Delete Test"
read -r -p "Do you want to unsubscribe/delete the test subscription now? [y/N]: " DO_DELETE
if [[ "$DO_DELETE" =~ ^[Yy]$ ]]; then
  api_call "UnSubscribe" POST "$BASE_URL/SPayAPI/Service/UnSubscribe" "$SUB_JSON" "$TOKEN"
  UNSUB_CODE="$(printf '%s' "$BODY" | jq -r '.responseCode // empty' 2>/dev/null || true)"
  if [[ "$UNSUB_CODE" == "1" || "$UNSUB_CODE" == "116" ]]; then
    ok "Unsubscribe/cleanup is complete or was already complete."
  else
    warn "Unsubscribe did not return the normal success/already-unsubscribed result."
  fi

  section "Final Deletion Verification"
  read -r -p "Press ENTER to re-run CheckSubscription and verify removal: " _
  api_call "CheckSubscription (after unsubscribe)" POST "$BASE_URL/SPayAPI/Service/CheckSubscription" "$SUB_JSON" "$TOKEN"
  FINAL_CODE="$(printf '%s' "$BODY" | jq -r '.responseCode // empty' 2>/dev/null || true)"
  if [[ "$FINAL_CODE" == "117" || "$FINAL_CODE" == "118" || "$FINAL_CODE" == "124" ]]; then
    ok "Final check indicates the MSISDN is no longer active/subscribed (code $FINAL_CODE)."
  elif [[ "$FINAL_CODE" == "1" ]]; then
    warn "Final check still reports an active subscription. Wait briefly and re-check; if persistent, inspect unsubscribe/backend processing."
  else
    warn "Final state is not conclusive. Review full response and recommendation."
  fi
else
  warn "Unsubscribe test skipped. The test subscription may remain active."
fi

section "Test Complete"
OUT_REPORT="./sdp_spay_test_report_$(date +%Y%m%d_%H%M%S).log"
cp "$REPORT" "$OUT_REPORT" 2>/dev/null || true
say "A sanitized diagnostic report was saved to: $OUT_REPORT"
say "For escalation, provide: test mode, API name, timestamp, HTTP status, responseCode, responseMessage, sanitized request, full response, source server IP, and relevant network/TLS evidence."
