#!/usr/bin/env bash

set -eo pipefail

testsDefinitions=${TESTSDEFINITIONSPATH:-"testsdefinitions.txt"}

TESTFLAGS=${TESTFLAGS:-"-cover"}
PARALLEL_TESTS_LIMIT=${PARALLEL_TESTS_LIMIT:-10}

CI_NODE_TOTAL=${CI_NODE_TOTAL:-1}
CI_NODE_INDEX=${CI_NODE_INDEX:-1}

output="regular"
coverMode="count"

if [[ ${TESTFLAGS} = *"-race"* ]]; then
    output="race"
    coverMode="atomic"
fi

printMessage() {
  echo -e "\\033[1m${*}\\033[0m"
}

joinBy() {
    local IFS="${1}"
    shift
    echo "${*}"
}

prepareTestCommands() {
    local definitions

    [[ ! -f ${testsDefinitions} ]] || rm ${testsDefinitions}

    for pkg in $(go list ./...); do
        local testIndex=0
        local runTests=()

        echo "Listing tests for ${pkg} package"

        local tempFile
        tempFile=$(mktemp)
        local exitCode=0

        # use env -i to clear parent environment variables for go test
        # shellcheck disable=SC2086
        ./scripts/go_test_no_env ${TESTFLAGS} -list "Test.*" "${pkg}" > "${tempFile}" || exitCode=99

        local tests
        tests=$(grep "^Test" "${tempFile}" || true)

        rm "${tempFile}"

        if [[ ${exitCode} -ne 0 ]]; then
            exit ${exitCode}
        fi

        if [[ -z "${tests}" ]]; then
            continue
        fi

        local counter=0
        for test in ${tests}; do
            counter=$((counter+1))
            runTests+=("${test}")

            if [[ ${counter} -ge ${PARALLEL_TESTS_LIMIT} ]]; then
                if [[ ${#runTests[@]} -gt 0 ]]; then
                    definitions=$(joinBy "|" "${runTests[@]}")
                    echo "${pkg} ${testIndex} ${definitions}" | tee -a ${testsDefinitions}
                fi

                counter=0
                runTests=()

                testIndex=$((testIndex+1))
            fi
        done

        if [[ ${#runTests[@]} -gt 0 ]]; then
            definitions=$(joinBy "|" "${runTests[@]}")
            echo "${pkg} ${testIndex} ${definitions}" | tee -a ${testsDefinitions}
        fi
    done
}

executeTestCommand() {
    local pkg=${1}
    local index=${2}
    local runTestsList=${3}

    local options=""

    local pkgSlug
    pkgSlug=$(echo "${pkg}" | tr "/" "-")

    local type
    if [[ ${TESTFLAGS} = *'tags=integration'* ]]; then
        type="integration"
    else
        type="unit"
    fi

    if [[ ${TESTFLAGS} = *'-cover'* ]]; then
        mkdir -p ".cover/${type}"
        mkdir -p ".testoutput/${type}"

        printMessage "\\n\\n--- Starting part ${index} of go ${type} tests of '${pkg}' package with coverprofile in '${coverMode}' mode:\\n"

        local profileFile=".cover/${type}/${pkgSlug}.${index}.${coverMode}.cover.txt"
        options="-covermode=${coverMode} -coverprofile=${profileFile} -coverpkg=${MAIN_PACKAGE}/..."
    else
        echo "Starting go ${type} test"
    fi

    local testOutputFile=".testoutput/${type}/${pkgSlug}.${index}.${output}.output.txt"

    local exitCode=0

    # use env -i to clear parent environment variables for go test
    # shellcheck disable=SC2086
    ./scripts/go_test_no_env ${options} ${TESTFLAGS} -ldflags "${GO_LDFLAGS}" -v "${pkg}" -run "${runTestsList}" 2>&1 | tee "${testOutputFile}" || exitCode=99

    return ${exitCode}
}

executeTestPart() {
    rm -rf .cover/ .testoutput/

    local numberOfDefinitions
    numberOfDefinitions=$(< "${testsDefinitions}" wc -l)
    local executionSize
    executionSize=$((numberOfDefinitions/CI_NODE_TOTAL+1))
    local nodeIndex=$((CI_NODE_INDEX-1))
    local executionOffset
    executionOffset=$((nodeIndex*executionSize+1))

    printMessage "Number of definitions: ${numberOfDefinitions}"
    printMessage "Suite size: ${CI_NODE_TOTAL}"
    printMessage "Suite index: ${CI_NODE_INDEX}"

    printMessage "Execution size: ${executionSize}"
    printMessage "Execution offset: ${executionOffset}"

    local exitCode=0
    while read -r pkg index tests; do
        executeTestCommand "${pkg}" "${index}" "${tests}" || exitCode=99
    done < <(tail -n +${executionOffset} ${testsDefinitions} | head -n ${executionSize})

    exit ${exitCode}
}

computeCoverageReport() {
    local reportDirectory="out/coverage"
    local sourceFile="${reportDirectory}/coverprofile.${output}.source.txt"
    local htmlReportFile="${reportDirectory}/coverprofile.${output}.html"
    local textReportFile="${reportDirectory}/coverprofile.${output}.txt"

    mkdir -p "${reportDirectory}"

    echo "mode: ${coverMode}" > ${sourceFile}
    grep -h -v -E -e "^mode:" -e "\/mock_[^\.]+\.go" .cover/*/*.${coverMode}.cover.txt >> ${sourceFile}

    printMessage "Generating HTML coverage report"
    go tool cover -o ${htmlReportFile} -html=${sourceFile}
    printMessage "Generating TXT coverage report"
    go tool cover -o ${textReportFile} -func=${sourceFile}

    printMessage "General coverage percentage:"
    local total
    total=$(grep "total" "${textReportFile}" || echo "")

    if [[ -n "${total}" ]]; then
        echo "${output} ${total}"
    fi
}

computeJUnitReport() {
    local reportDirectory="out/junit"
    local concatenatedOutputFile="/tmp/test-output.txt"

    mkdir -p "${reportDirectory}"

    cat .testoutput/*/*.${output}.output.txt > "${concatenatedOutputFile}"

    go-junit-report < "${concatenatedOutputFile}" > "${reportDirectory}/report.xml"
}

case "$1" in
    prepare)
        prepareTestCommands
        ;;
    execute)
        executeTestPart
        ;;
    coverage)
        computeCoverageReport
        ;;
    junit)
        computeJUnitReport
        ;;
esac
