#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REQUEST_DIR="${SCRIPT_DIR}/../src/test/resources/requests"
VALID_REQUEST="${REQUEST_DIR}/exam-sprint-achievement-report-request.json"
INVALID_REQUEST="${REQUEST_DIR}/exam-sprint-achievement-report-invalid-request.json"
BASE_URL="${BASE_URL:-http://127.0.0.1:8080}"
OUTPUT_PATH="${OUTPUT_PATH:-${PWD}/achievement-report-demo.pdf}"
HTML_OUTPUT_PATH="${HTML_OUTPUT_PATH:-${PWD}/achievement-report-preview.html}"
MAX_ATTEMPTS="${MAX_ATTEMPTS:-30}"
POLL_INTERVAL_SECONDS="${POLL_INTERVAL_SECONDS:-1}"
JSON_PARSER=""
TMP_FILES=()

usage() {
  cat <<EOF
Usage:
  $(basename "$0") success
  $(basename "$0") invalid
  $(basename "$0") --help

Modes:
  success   Submit the valid sample request, poll report status, then download the PDF and preview HTML.
  invalid   Submit the invalid sample request and print the validation error response.

Environment variables:
  BASE_URL               API base URL. Default: http://127.0.0.1:8080
  OUTPUT_PATH            PDF output path for success mode.
                         Default: current working directory/achievement-report-demo.pdf
  HTML_OUTPUT_PATH       HTML preview output path for success mode.
                         Default: current working directory/achievement-report-preview.html
  MAX_ATTEMPTS           Polling attempts for success mode. Default: 30
  POLL_INTERVAL_SECONDS  Seconds between polling attempts. Default: 1

Examples:
  $(basename "$0") success
  BASE_URL=http://127.0.0.1:8081 $(basename "$0") invalid
EOF
}

cleanup() {
  if [ "${#TMP_FILES[@]}" -gt 0 ]; then
    rm -f "${TMP_FILES[@]}"
  fi
}

trap cleanup EXIT

die() {
  printf 'Error: %s\n' "$1" >&2
  exit 1
}

make_temp_file() {
  local file
  file="$(mktemp)"
  TMP_FILES+=("$file")
  printf '%s\n' "$file"
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"
}

select_json_parser() {
  if command -v jq >/dev/null 2>&1; then
    JSON_PARSER="jq"
  elif command -v python3 >/dev/null 2>&1; then
    JSON_PARSER="python3"
  else
    die "JSON parsing requires jq or python3, but neither was found in PATH."
  fi
}

json_get() {
  local file="$1"
  local path="$2"

  if [ "$JSON_PARSER" = "jq" ]; then
    jq -er ".$path" "$file"
  else
    python3 - "$file" "$path" <<'PY'
import json
import sys
from pathlib import Path

file_path = Path(sys.argv[1])
path = [part for part in sys.argv[2].split('.') if part]
value = json.loads(file_path.read_text(encoding='utf-8'))

for part in path:
    if isinstance(value, dict) and part in value:
        value = value[part]
    else:
        sys.exit(1)

if value is None:
    sys.exit(1)

if isinstance(value, (dict, list)):
    print(json.dumps(value, ensure_ascii=False))
else:
    print(value)
PY
  fi
}

pretty_print_json() {
  local file="$1"

  if [ "$JSON_PARSER" = "jq" ]; then
    jq . "$file"
  else
    python3 - "$file" <<'PY'
import json
import sys
from pathlib import Path

file_path = Path(sys.argv[1])
data = json.loads(file_path.read_text(encoding='utf-8'))
print(json.dumps(data, ensure_ascii=False, indent=2))
PY
  fi
}

normalize_url() {
  local url="$1"

  if [[ "$url" =~ ^https?:// ]]; then
    printf '%s\n' "$url"
  elif [[ "$url" == /* ]]; then
    printf '%s%s\n' "${BASE_URL%/}" "$url"
  else
    printf '%s/%s\n' "${BASE_URL%/}" "$url"
  fi
}

post_json() {
  local url="$1"
  local payload="$2"
  local output="$3"

  curl -sS -o "$output" -w '%{http_code}' -X POST -H 'Content-Type: application/json' --data "@$payload" "$url"
}

get_json() {
  local url="$1"
  local output="$2"

  curl -sS -o "$output" -w '%{http_code}' "$url"
}

download_file() {
  local url="$1"
  local output="$2"

  curl -sS -L -o "$output" -w '%{http_code}' "$url"
}

ensure_request_files() {
  [ -f "$VALID_REQUEST" ] || die "Missing request fixture: $VALID_REQUEST"
  [ -f "$INVALID_REQUEST" ] || die "Missing request fixture: $INVALID_REQUEST"
}

run_success_mode() {
  local submit_url submit_body submit_status report_id query_url query_body query_status current_status
  local download_url download_target download_status preview_html_url preview_html_target preview_html_status attempt

  submit_url="${BASE_URL%/}/api/exam-sprint/reports"
  submit_body="$(make_temp_file)"
  submit_status="$(post_json "$submit_url" "$VALID_REQUEST" "$submit_body")" || die "Failed to submit request to ${submit_url}"

  if [ "$submit_status" != "202" ]; then
    printf 'Submit request failed with HTTP %s\n' "$submit_status" >&2
    pretty_print_json "$submit_body" >&2
    exit 1
  fi

  report_id="$(json_get "$submit_body" 'data.reportId')" || die "Accepted response did not contain data.reportId"
  printf 'Report created: %s\n' "$report_id"

  query_url="${BASE_URL%/}/api/exam-sprint/reports/${report_id}"
  query_body="$(make_temp_file)"

  for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do
    query_status="$(get_json "$query_url" "$query_body")" || die "Failed to query report status from ${query_url}"
    if [ "$query_status" != "200" ]; then
      printf 'Query report failed with HTTP %s\n' "$query_status" >&2
      pretty_print_json "$query_body" >&2
      exit 1
    fi

    current_status="$(json_get "$query_body" 'data.generationStatus')" || die "Report response did not contain data.generationStatus"
    printf 'Poll %d/%s: %s\n' "$attempt" "$MAX_ATTEMPTS" "$current_status"

    case "$current_status" in
      SUCCESS)
        download_url="$(json_get "$query_body" 'data.downloadUrl')" || die "Successful report did not contain data.downloadUrl"
        download_url="$(normalize_url "$download_url")"
        download_target="$(make_temp_file)"
        download_status="$(download_file "$download_url" "$download_target")" || die "Failed to download PDF from ${download_url}"
        if [ "$download_status" != "200" ]; then
          printf 'Download failed with HTTP %s\n' "$download_status" >&2
          pretty_print_json "$download_target" >&2
          exit 1
        fi

        preview_html_url="$(json_get "$query_body" 'data.previewHtmlUrl')" || die "Successful report did not contain data.previewHtmlUrl"
        preview_html_url="$(normalize_url "$preview_html_url")"
        preview_html_target="$(make_temp_file)"
        preview_html_status="$(download_file "$preview_html_url" "$preview_html_target")" || die "Failed to download preview HTML from ${preview_html_url}"
        if [ "$preview_html_status" != "200" ]; then
          printf 'Preview HTML download failed with HTTP %s\n' "$preview_html_status" >&2
          pretty_print_json "$preview_html_target" >&2
          exit 1
        fi

        mv "$download_target" "$OUTPUT_PATH"
        mv "$preview_html_target" "$HTML_OUTPUT_PATH"
        printf 'PDF downloaded to: %s\n' "$OUTPUT_PATH"
        printf 'Preview HTML downloaded to: %s\n' "$HTML_OUTPUT_PATH"
        return 0
        ;;
      FAILED|EXPIRED)
        printf 'Report ended with status %s\n' "$current_status" >&2
        pretty_print_json "$query_body" >&2
        exit 1
        ;;
    esac

    sleep "$POLL_INTERVAL_SECONDS"
  done

  printf 'Report did not reach SUCCESS within %s attempts\n' "$MAX_ATTEMPTS" >&2
  pretty_print_json "$query_body" >&2
  exit 1
}

run_invalid_mode() {
  local submit_url response_body response_status

  submit_url="${BASE_URL%/}/api/exam-sprint/reports"
  response_body="$(make_temp_file)"
  response_status="$(post_json "$submit_url" "$INVALID_REQUEST" "$response_body")" || die "Failed to submit invalid request to ${submit_url}"

  printf 'HTTP %s\n' "$response_status"
  pretty_print_json "$response_body"

  if [ "$response_status" != "400" ]; then
    die "Expected HTTP 400 for invalid request, got ${response_status}"
  fi
}

main() {
  local mode="${1:-}"

  if [ "$#" -gt 1 ]; then
    usage
    exit 1
  fi

  case "$mode" in
    ""|--help|-h|help)
      usage
      ;;
    success)
      require_command curl
      select_json_parser
      ensure_request_files
      run_success_mode
      ;;
    invalid)
      require_command curl
      select_json_parser
      ensure_request_files
      run_invalid_mode
      ;;
    *)
      usage
      exit 1
      ;;
  esac
}

main "$@"
