#! /bin/bash

set -euo pipefail


HOSTURL="${1:-http://localhost:8123}"


#------------------------------------------------------------------------------------------------
SCRIPT_DIR=$(dirname "$0")
SCRIPT_NAME=$(basename "$0")

# FUNCTIONS
function title() {
    echo "----------------------------------------"
    # Center the text
    local text="$1"
    local line_length=40
    local text_length=${#text}
    local padding=$(( (line_length - text_length) / 2 ))
    printf "%*s%s%*s\n" $padding "" "$text" $padding ""
    echo "----------------------------------------"
}   


function die() {
    title "error: $1"
    exit 1
}


#------------------------------------------------------------------------------------------------
cat << EOF


EOF

title "TESTING ${HOSTURL}"

cat << EOF


EOF

# Test 0: Verify the script is running
title "0: Verify the server is running"


# test jq is installed
if ! command -v jq &> /dev/null; then
    echo "jq could not be found"
    echo "sudo apt-get install jq"
    exit 1
fi

# read ~/.config/simple_object_storage/config.json
CONFIG_PATH="${SCRIPT_DIR}/.test-docker/config.json"
if [ ! -f "${CONFIG_PATH}" ]; then
    echo "config file not found at ${CONFIG_PATH}"
    exit 1
fi
CONFIG=$(cat "${CONFIG_PATH}")

# extract the first write token from the config
WRITE_TOKEN=$(echo "$CONFIG" | jq -r '.write_tokens[0]')

BASE_TAG="autotest"


# test if server is running
if ! curl -s "${HOSTURL}/status" | jq -r '.result' | grep -q 'success'; then
    die "server is not running on ${HOSTURL}"
fi

echo "Simple Object Storage server is running at ${HOSTURL}"

 # test every action in the README.md file, leaving the system in the same state it was found
 # and print the output of each action

# Construct metadata JSON
METADATA_JSON=$(cat <<EOF
{
  "labeltags": ["${BASE_TAG}:test1"],
  "description": "Example file",
  "custom_field": "custom value"
}
EOF
)

#------------------------------------------------------------------------------------------------
# Test 1: Verify extra metadata fields are preserved
title "1: Upload script to ${BASE_TAG}:test1"

# upload this script as an object
echo "uploading ${SCRIPT_DIR}/${SCRIPT_NAME} to ${BASE_TAG}:test1"
UPLOAD_RESPONSE=$(curl -X PUT \
  -H "Authorization: Bearer ${WRITE_TOKEN}" \
  -F "file=@${SCRIPT_DIR}/${SCRIPT_NAME}" \
  -F "metadata=${METADATA_JSON}" \
  "${HOSTURL}/upload")

echo "upload response: ${UPLOAD_RESPONSE}"

OBJECT_HASH=$(echo "${UPLOAD_RESPONSE}" | jq -r '.hash')

#------------------------------------------------------------------------------------------------
title "2: Check sos hash matches"

# check the hash matches.
CMD="${HOSTURL}/hash/${BASE_TAG}:test1"
echo "checking hash via ${CMD}"
CHECK_HASH=$(curl -s "${CMD}" | jq -r '.hash')
[ "${OBJECT_HASH}" != "${CHECK_HASH}" ] && die "hash does not match: ${OBJECT_HASH} != ${CHECK_HASH}"


#------------------------------------------------------------------------------------------------
title "3: Check MD5Sum matches, for both label:tag and hash downloads"

# get md5sum of this file
MD5SUM=$(md5sum "${SCRIPT_DIR}/${SCRIPT_NAME}" | awk '{print $1}')
echo "md5sum of ${SCRIPT_DIR}/${SCRIPT_NAME} is ${MD5SUM}"

# download the object
echo "downloading ${OBJECT_HASH} to ${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded1"
if ! curl -s "${HOSTURL}/${OBJECT_HASH}" -o "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded1"; then
    die "failed to download ${OBJECT_HASH}"
fi

# download the object again via the label:tag
echo "downloading ${BASE_TAG}:test1 to ${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded2"
if ! curl -s "${HOSTURL}/${BASE_TAG}:test1" -o "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded2"; then
    die "failed to download ${BASE_TAG}:test1"
fi

# get md5sum of the downloaded file
MD5SUM_DOWNLOADED1=$(md5sum "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded1" | awk '{print $1}')
echo "md5sum of ${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded1 is ${MD5SUM_DOWNLOADED1}"
[ "${MD5SUM}" != "${MD5SUM_DOWNLOADED1}" ] && die "md5sums do not match"
MD5SUM_DOWNLOADED2=$(md5sum "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded2" | awk '{print $1}')
[ "${MD5SUM}" != "${MD5SUM_DOWNLOADED2}" ] && die "md5sums do not match"

rm "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded1"
rm "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded2"

#------------------------------------------------------------------------------------------------
LABELTAG="finangle-wrangler:fuzzy_test"
title "3b: Upload script to ${LABELTAG}"


METADATA_JSON=$(cat <<EOF
{
  "labeltags": ["${LABELTAG}"],
  "description": "Test File for fuzzy test",
  "custom_field": "Upload2!!!111 Yay. This is a test file for the fuzzy test."
}
EOF
)
# upload this script as an object
echo "uploading ${SCRIPT_DIR}/${SCRIPT_NAME} to ${LABELTAG}"
UPLOAD_RESPONSE=$(curl -X PUT \
  -H "Authorization: Bearer ${WRITE_TOKEN}" \
  -F "file=@${SCRIPT_DIR}/${SCRIPT_NAME}" \
  -F "metadata=${METADATA_JSON}" \
  "${HOSTURL}/upload")

echo "upload response: ${UPLOAD_RESPONSE}"

OBJECT_HASH2=$(echo "${UPLOAD_RESPONSE}" | jq -r '.hash')

if [ ! "${OBJECT_HASH}" == "${OBJECT_HASH2}" ]; then
    die "object hash does not match: ${OBJECT_HASH} != ${OBJECT_HASH2}"
fi

# test the description and custom field matches our new ones.
METADATA_RESPONSE=$(curl "${HOSTURL}/meta/${OBJECT_HASH2}")

echo "metadata response: ${METADATA_RESPONSE}"

if ! echo "${METADATA_RESPONSE}" | jq -r '.metadata.description' | grep -q "Test File for fuzzy test"; then
    die "description does not match"
fi

if ! echo "${METADATA_RESPONSE}" | jq -r '.metadata.custom_field' | grep -q "Upload2!!!111 Yay. This is a test file for the fuzzy test."; then
    die "custom field does not match"
fi

# download via the label:tag
echo "downloading ${LABELTAG} to ${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded3"
if ! curl -s "${HOSTURL}/${LABELTAG}" -o "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded3"; then
    die "failed to download ${LABELTAG}"
fi

# get md5sum of the downloaded file
MD5SUM_DOWNLOADED3=$(md5sum "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded3" | awk '{print $1}')
echo "md5sum of ${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded3 is ${MD5SUM_DOWNLOADED3}"
[ "${MD5SUM}" != "${MD5SUM_DOWNLOADED3}" ] && die "md5sums do not match"

rm "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded3"



#------------------------------------------------------------------------------------------------
title "4: Delete the object"

# delete the object
echo "deleting ${OBJECT_HASH}"
if ! curl -s -H "Authorization: Bearer ${WRITE_TOKEN}" "${HOSTURL}/deleteobject?hash=${OBJECT_HASH}" | jq -r '.result' | grep -q 'success'; then
    die "failed to delete ${OBJECT_HASH}"
fi

# verify the object is deleted
echo "verifying ${OBJECT_HASH} is deleted"
DELETE_RESPONSE=$(curl -s "${HOSTURL}/${OBJECT_HASH}")
if ! echo "${DELETE_RESPONSE}" | jq -r '.result' | grep -q 'error'; then
    die "failed to verify ${OBJECT_HASH} is deleted"
fi

#------------------------------------------------------------------------------------------------
title "5: Test metadata field preservation"

# Upload with extra metadata fields
EXTRA_METADATA_JSON=$(cat <<EOF
{
  "labeltags": ["${BASE_TAG}:test2"],
  "description": "Test with extra fields",
  "custom_field": "custom value",
  "extra_field1": "value1",
  "extra_field2": "value2"
}
EOF
)

echo "uploading with extra metadata fields"
UPLOAD_RESPONSE=$(curl -X PUT \
  -H "Authorization: Bearer ${WRITE_TOKEN}" \
  -F "file=@${SCRIPT_DIR}/${SCRIPT_NAME}" \
  -F "metadata=${EXTRA_METADATA_JSON}" \
  "${HOSTURL}/upload")
UPLOAD_EXIT_CODE=$?
echo "Upload response: ${UPLOAD_RESPONSE}"
echo "Upload exit code: ${UPLOAD_EXIT_CODE}"

if [ ${UPLOAD_EXIT_CODE} -ne 0 ]; then
    die "Failed to upload object: curl returned ${UPLOAD_EXIT_CODE}"
fi

if ! echo "${UPLOAD_RESPONSE}" | jq -e . >/dev/null 2>&1; then
    die "Invalid JSON response from upload: ${UPLOAD_RESPONSE}"
fi

OBJECT_HASH=$(echo "${UPLOAD_RESPONSE}" | jq -r '.hash')
echo "Received hash: ${OBJECT_HASH}"

# Verify the object exists
echo "Verifying object exists..."
EXISTS_RESPONSE=$(curl -s "${HOSTURL}/exists/${BASE_TAG}:test2")
echo "Exists response: ${EXISTS_RESPONSE}"

# Get metadata and verify extra fields are preserved
echo "Retrieving metadata for ${BASE_TAG}:test2"
METADATA_RESPONSE=$(curl -s "${HOSTURL}/meta/${BASE_TAG}:test2")
CURL_EXIT_CODE=$?
echo "Curl exit code: ${CURL_EXIT_CODE}"
echo "Full metadata response: ${METADATA_RESPONSE}"

if [ ${CURL_EXIT_CODE} -ne 0 ]; then
    die "Failed to retrieve metadata: curl returned ${CURL_EXIT_CODE}"
fi

if ! echo "${METADATA_RESPONSE}" | jq -e . >/dev/null 2>&1; then
    die "Invalid JSON response: ${METADATA_RESPONSE}"
fi

if ! echo "${METADATA_RESPONSE}" | jq -r '.metadata.extra_field1' | grep -q 'value1'; then
    die "extra_field1 not preserved in metadata"
fi
if ! echo "${METADATA_RESPONSE}" | jq -r '.metadata.extra_field2' | grep -q 'value2'; then
    die "extra_field2 not preserved in metadata"
fi

#------------------------------------------------------------------------------------------------
title "6: Test tag versioning behavior"

# Clean up
curl -s -H "Authorization: Bearer ${WRITE_TOKEN}" "${HOSTURL}/deleteobject?hash=${OBJECT_HASH}" > /dev/null

# Upload first version with tag 'latest'
FIRST_METADATA_JSON=$(cat <<EOF
{
  "labeltags": ["${BASE_TAG}:latest", "${BASE_TAG}:v1"],
  "description": "First version"
}
EOF
)

echo "uploading first version with tag 'latest'"
UPLOAD_RESPONSE=$(curl -X PUT \
  -H "Authorization: Bearer ${WRITE_TOKEN}" \
  -F "file=@${SCRIPT_DIR}/${SCRIPT_NAME}" \
  -F "metadata=${FIRST_METADATA_JSON}" \
  "${HOSTURL}/upload")

FIRST_HASH=$(echo "${UPLOAD_RESPONSE}" | jq -r '.hash')

# Store first version's metadata before uploading second version
FIRST_METADATA=$(curl -s "${HOSTURL}/meta/${FIRST_HASH}")
echo "First version metadata response: ${FIRST_METADATA}"
if ! echo "${FIRST_METADATA}" | jq -r '.metadata.labeltags[]' | grep -q "${BASE_TAG}:v1"; then
    die "First version does not have v1 tag"
fi

# Upload second version with same tag 'latest'
SECOND_METADATA_JSON=$(cat <<EOF
{
  "labeltags": ["${BASE_TAG}:latest", "${BASE_TAG}:v2"],
  "description": "Second version"
}
EOF
)

if [ ! -f "${SCRIPT_DIR}/test_1GB_file_upload.sh" ]; then
    die "test_1GB_file_upload.sh not found"
fi

echo "uploading second version with tag 'latest'"
UPLOAD_RESPONSE=$(curl -X PUT \
  -H "Authorization: Bearer ${WRITE_TOKEN}" \
  -F "file=@${SCRIPT_DIR}/test_1GB_file_upload.sh" \
  -F "metadata=${SECOND_METADATA_JSON}" \
  "${HOSTURL}/upload")

SECOND_HASH=$(echo "${UPLOAD_RESPONSE}" | jq -r '.hash')

# Verify first version's metadata still has v1 tag
FIRST_METADATA=$(curl -s "${HOSTURL}/meta/${FIRST_HASH}")
echo "First version metadata response: ${FIRST_METADATA}"
if ! echo "${FIRST_METADATA}" | jq -r '.metadata.labeltags[]' | grep -q "${BASE_TAG}:v1"; then
    die "First version does not have v1 tag"
fi

# Verify first version's metadata no longer has the latest tag
if echo "${FIRST_METADATA}" | jq -r '.metadata.labeltags[]' | grep -q "${BASE_TAG}:latest"; then
    die "First version still has latest tag"
fi

# Verify second version has the correct tags: v2 and latest
SECOND_METADATA=$(curl -s "${HOSTURL}/meta/${SECOND_HASH}")
echo "Second version metadata response: ${SECOND_METADATA}"
if ! echo "${SECOND_METADATA}" | jq -r '.metadata.labeltags[]' | grep -q "${BASE_TAG}:latest"; then
    die "Second version does not have latest tag"
fi
if ! echo "${SECOND_METADATA}" | jq -r '.metadata.labeltags[]' | grep -q "${BASE_TAG}:v2"; then
    die "Second version does not have v2 tag"
fi

#------------------------------------------------------------------------------------------------
title "7: Test rate limiting behavior"


# Clean up
curl -s -H "Authorization: Bearer ${WRITE_TOKEN}" "${HOSTURL}/deleteobject?hash=${FIRST_HASH}" > /dev/null
curl -s -H "Authorization: Bearer ${WRITE_TOKEN}" "${HOSTURL}/deleteobject?hash=${SECOND_HASH}" > /dev/null

# Use a known invalid token
INVALID_TOKEN="invalid_token"

# Make 5 requests with an invalid token
for i in {1..5}; do
    echo "Attempt $i with invalid token"
    RESPONSE=$(curl -s -X PUT -H "Authorization: Bearer ${INVALID_TOKEN}" -F "file=@${SCRIPT_DIR}/${SCRIPT_NAME}" -F "metadata={\"labeltags\":[\"test:latest\"]}" "${HOSTURL}/upload")
    echo "Response: ${RESPONSE}"
done

# Now try a request with a valid token - should be rate limited
echo "Attempting request with valid token (should be rate limited)"
RESPONSE=$(curl -s -X PUT -H "Authorization: Bearer ${WRITE_TOKEN}" -F "file=@${SCRIPT_DIR}/${SCRIPT_NAME}" -F "metadata={\"labeltags\":[\"test:latest\"]}" "${HOSTURL}/upload")
if ! echo "${RESPONSE}" | jq -r '.error' | grep -q "Too many authentication attempts"; then
    die "Expected rate limit error, got: ${RESPONSE}"
fi

echo "Sleeping for 3 seconds to allow rate limit to reset"
echo "(Normally 5 mins, but we set to 2s for this test!)"

sleep 3
# Now try a request with a valid token - should be rate limited
echo "Attempting request with valid token (should NOT be rate limited)"
RESPONSE=$(curl -s -X PUT -H "Authorization: Bearer ${WRITE_TOKEN}" -F "file=@${SCRIPT_DIR}/${SCRIPT_NAME}" -F "metadata={\"labeltags\":[\"test:latest\"]}" "${HOSTURL}/upload")
if echo "${RESPONSE}" | jq -r '.error' | grep -q "Too many authentication attempts"; then
    die "Expected no rate limit error, got: ${RESPONSE}"
fi

# delete the object
TODELHASH=$(curl -s "${HOSTURL}/hash/test:latest" | jq -r '.hash')
echo "deleting test:latest ${TODELHASH}"

if ! curl -s -H "Authorization: Bearer ${WRITE_TOKEN}" "${HOSTURL}/deleteobject?hash=${TODELHASH}" | jq -r '.result' | grep -q 'success'; then
    die "failed to delete ${TODELHASH}"
fi

#------------------------------------------------------------------------------------------------
title "ALL TESTS PASSED"
