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

# Manual end-to-end probe for POST /api/satellite/upload strict metadata
# validation (AZ-810). Each failure call should return HTTP 400 with an
# `application/problem+json` body. The happy-path call should return HTTP 200
# with the per-item result envelope.
#
# Three enforcement layers compose at the endpoint:
#   1. UnmappedMemberHandling.Disallow + [JsonRequired] on the metadata DTO
#      — the UavUploadValidationFilter deserializes the `metadata` form field
#      via the strict global JsonSerializerOptions and surfaces JsonException
#      under `errors["metadata"]`. Covers missing-required, unknown fields,
#      type mismatches, malformed UUIDs (AZ-810 rules 3, 12, 13, 14).
#   2. FluentValidation (UavTileBatchMetadataPayloadValidator +
#      UavTileMetadataValidator) — per-item range checks (lat, lon, tileZoom,
#      tileSizeMeters, capturedAt freshness) and the items.Count <=
#      MaxBatchSize cap. Errors are prefixed with `metadata.` so paths look
#      like `errors["metadata.items[0].latitude"]`. Covers AZ-810 rules 4-5,
#      7-11.
#   3. Cross-field envelope rule (items.Count == files.Count) — surfaces
#      under both `errors["metadata.items"]` AND `errors["files"]`. Covers
#      AZ-810 rule 6.
#
# Auth: the endpoint requires JWT bearer + the `permissions` claim must
# contain `GPS` (AZ-487 / AZ-488). Mint a token via:
#   dotnet run --project SatelliteProvider.IntegrationTests -- --mint-only
# then jq the `permissions` claim into the GPS group, or use the GPS-specific
# minter helper if one is exposed.
#
# Usage:
#   API_URL=https://localhost:8080 JWT="<bearer-token-with-GPS-permission>" \
#       ./scripts/probe_upload_validation.sh

API_URL="${API_URL:-https://localhost:8080}"
JWT="${JWT:-}"
ENDPOINT="${API_URL%/}/api/satellite/upload"
TMPDIR="${TMPDIR:-/tmp}"
JPEG_PATH="${TMPDIR}/probe_upload_validation.jpg"

if [[ -z "${JWT}" ]]; then
    echo "ERROR: set JWT env var to a bearer token whose 'permissions' claim contains 'GPS'."
    echo "  Mint a default token (no GPS claim) via:"
    echo "    dotnet run --project SatelliteProvider.IntegrationTests -- --mint-only"
    echo "  Then attach the GPS permission claim manually or use a GPS-specific minter."
    exit 2
fi

# Emit a tiny valid JPEG (FF D8 FF D9 = empty SOI/EOI; the endpoint's
# UavTileQualityGate Rule 1 only inspects the magic bytes, not full decode,
# but Rules 2 / 3 / 5 will reject it. Since AZ-810 validation runs BEFORE the
# quality gate, the validator's verdict is what we're probing here. A tiny
# placeholder keeps multipart bodies small.)
printf '\xff\xd8\xff\xd9' > "${JPEG_PATH}"

curl_args=(-sS -k -H "Authorization: Bearer ${JWT}")

probe() {
    local label="$1"
    local metadata="$2"
    local files_arg="$3"   # quoted -F arg-list, e.g. -F 'files=@/tmp/x.jpg;type=image/jpeg'
    local expected_status="$4"

    echo "----- ${label} (expecting HTTP ${expected_status}) -----"
    local response
    # shellcheck disable=SC2086
    response=$(curl "${curl_args[@]}" -X POST \
        -F "metadata=${metadata}" \
        ${files_arg} \
        "${ENDPOINT}" -w "\nHTTP_STATUS=%{http_code}\n")
    echo "${response}"
    local actual_status
    actual_status=$(echo "${response}" | tail -n 1 | sed 's/HTTP_STATUS=//')
    if [[ "${actual_status}" != "${expected_status}" ]]; then
        echo "FAIL: expected HTTP ${expected_status}, got ${actual_status}"
        return 1
    fi
    echo "OK: HTTP ${expected_status}"
    echo
}

# AC-2: happy path (well-formed envelope + 1 file)
happy_metadata='{"items":[{"latitude":50.10,"longitude":36.10,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"}]}'
probe "happy-path" "${happy_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 200

# Rule 2: missing metadata form field
echo "----- missing-metadata-field (expecting HTTP 400) -----"
response=$(curl "${curl_args[@]}" -X POST \
    -F "files=@${JPEG_PATH};type=image/jpeg" \
    "${ENDPOINT}" -w "\nHTTP_STATUS=%{http_code}\n")
echo "${response}"
actual_status=$(echo "${response}" | tail -n 1 | sed 's/HTTP_STATUS=//')
if [[ "${actual_status}" != "400" ]]; then
    echo "FAIL: expected HTTP 400, got ${actual_status}"
    exit 1
fi
echo "OK: HTTP 400"
echo

# Rule 3: malformed metadata JSON
probe "malformed-json" '{"items": [{ "latitude": 50.10, "longitude": 36.10' \
    "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 4: empty items
probe "empty-items" '{"items": []}' "" 400

# Rule 6: items.Count != files.Count (2 items, 1 file)
mismatch_metadata='{"items":[{"latitude":50.10,"longitude":36.10,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"},{"latitude":50.11,"longitude":36.11,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"}]}'
probe "items-files-mismatch" "${mismatch_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 7: lat out of range
lat_metadata='{"items":[{"latitude":91.0,"longitude":36.10,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"}]}'
probe "lat-out-of-range" "${lat_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 8: lon out of range
lon_metadata='{"items":[{"latitude":50.10,"longitude":181.0,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"}]}'
probe "lon-out-of-range" "${lon_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 9: tileZoom out of range
zoom_metadata='{"items":[{"latitude":50.10,"longitude":36.10,"tileZoom":30,"tileSizeMeters":200.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"}]}'
probe "tileZoom-out-of-range" "${zoom_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 10: tileSizeMeters non-positive
size_metadata='{"items":[{"latitude":50.10,"longitude":36.10,"tileZoom":18,"tileSizeMeters":0.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"}]}'
probe "tileSizeMeters-non-positive" "${size_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 11a: capturedAt in the future (use a date 1 year out for portability)
future_iso="$(date -u -v+1y +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d '+1 year' +"%Y-%m-%dT%H:%M:%SZ")"
future_metadata='{"items":[{"latitude":50.10,"longitude":36.10,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"${future_iso}"'"}]}'
probe "capturedAt-future" "${future_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 11b: capturedAt too old (60 days)
old_iso="$(date -u -v-60d +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d '60 days ago' +"%Y-%m-%dT%H:%M:%SZ")"
old_metadata='{"items":[{"latitude":50.10,"longitude":36.10,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"${old_iso}"'"}]}'
probe "capturedAt-too-old" "${old_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 12: malformed flightId UUID
flight_metadata='{"items":[{"latitude":50.10,"longitude":36.10,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'","flightId":"not-a-uuid"}]}'
probe "flightId-malformed" "${flight_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 13: unknown root field
unknown_root_metadata='{"items":[{"latitude":50.10,"longitude":36.10,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"}],"debug":"fingerprint"}'
probe "unknown-root-field" "${unknown_root_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 13b: unknown nested field
unknown_nested_metadata='{"items":[{"latitude":50.10,"longitude":36.10,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'","altitude":500.0}]}'
probe "unknown-nested-field" "${unknown_nested_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

# Rule 14: type mismatch (latitude as string)
type_mismatch_metadata='{"items":[{"latitude":"fifty","longitude":36.10,"tileZoom":18,"tileSizeMeters":200.0,"capturedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"}]}'
probe "lat-type-mismatch" "${type_mismatch_metadata}" "-F files=@${JPEG_PATH};type=image/jpeg" 400

echo "All probes passed."
