#!/bin/bash

DIR="$(dirname "${BASH_SOURCE[0]}")"

DIR="$(realpath "${DIR}")"

ROOT_DIR="$(realpath "${DIR}/../..")"

export TARGET_ORG="${TARGET_ORG:-actions-runner-controller}"
export TARGET_REPO="${TARGET_REPO:-arc_e2e_test_dummy}"
export IMAGE_NAME="${IMAGE_NAME:-arc-test-image}"

function log() {
    echo "[$(date -u +%FT%T%Z)] $*" >&2
}

# Trims a single pair of matching surrounding quotes from the provided string.
# Examples:
#   trim_quotes '"1.2.3"' -> 1.2.3
#   trim_quotes "'v1'"   -> v1
function trim_quotes() {
    local s
    s="$*"

    if [[ ${#s} -ge 2 ]]; then
        local first last
        first="${s:0:1}"
        last="${s: -1}"
        if [[ ( "${first}" == '"' && "${last}" == '"' ) || ( "${first}" == "'" && "${last}" == "'" ) ]]; then
            s="${s:1:${#s}-2}"
        fi
    fi

    printf '%s\n' "${s}"
}

# Tests decide which chart version to use. Helper provides extraction utilities.
function chart_version() {
    local chart_yaml="$1"
    if [[ -z "${chart_yaml}" ]] || [[ ! -f "${chart_yaml}" ]]; then
        echo "Chart.yaml not found: ${chart_yaml}" >&2
        return 1
    fi

    local version
    version=""

    if command -v yq >/dev/null 2>&1; then
        # Prefer yq v4+ syntax, but accept older variants.
        version="$(yq -r '.version' "${chart_yaml}" 2>/dev/null || true)"
        if [[ -z "${version}" ]] || [[ "${version}" == "null" ]]; then
            version="$(yq '.version' <"${chart_yaml}" 2>/dev/null || true)"
        fi
    fi

    # Fallback for environments without yq.
    if [[ -z "${version}" ]] || [[ "${version}" == "null" ]]; then
        version="$(awk -F: 'tolower($1)=="version" {sub(/^[[:space:]]+/,"",$2); print $2; exit}' "${chart_yaml}" 2>/dev/null || true)"
    fi

    version="$(trim_quotes "${version}" | tr -d "[:space:]")"
    if [[ -z "${version}" ]]; then
        log "Failed to extract version from ${chart_yaml} via yq"
        return 1
    fi
    printf '%s\n' "${version}"
    return 0
}

function ensure_version_set() {
    if [[ -z "${VERSION:-}" ]]; then
        log 'VERSION is not set. Set it in the test, e.g. export VERSION="$(chart_version path/to/Chart.yaml)".'
        return 1
    fi

    # Defensive: if a tool produced quoted output, normalize it before using in tags/args.
    export VERSION="$(printf '%s' "${VERSION}" | tr -d "\"'[:space:]")"
    if [[ -z "${VERSION}" ]]; then
        log "VERSION resolved to an empty value"
        return 1
    fi

    export IMAGE_TAG="${IMAGE_TAG:-${VERSION}}"
    export IMAGE="${IMAGE:-${IMAGE_NAME}:${IMAGE_TAG}}"
}

export PLATFORMS="linux/amd64"
COMMIT_SHA="$(git rev-parse HEAD)"
export COMMIT_SHA

function build_image() {
    ensure_version_set || return 1
    log "Building ARC image ${IMAGE}"

    cd "${ROOT_DIR}" || exit 1

    docker buildx build --platform "${PLATFORMS}" \
        --build-arg VERSION="${VERSION}" \
        --build-arg COMMIT_SHA="${COMMIT_SHA}" \
        -t "${IMAGE}" \
        -f Dockerfile \
        . --load

    echo "Created image ${IMAGE}"
    cd - || exit 1
}

function create_cluster() {
    ensure_version_set || return 1
    log "Deleting minikube cluster if exists"
    minikube delete || true

    log "Creating minikube cluster"
    minikube start --driver=docker --container-runtime=docker --wait=all

    log "Verifying ns works"
    if ! minikube ssh "nslookup github.com >/dev/null 2>&1"; then
        log "Nameserver configuration failed"
        exit 1
    fi

    log "Loading image into minikube cluster"
    minikube image load "${IMAGE}"

    log "Loading runner image into minikube cluster"
    minikube image load "ghcr.io/actions/actions-runner:latest"
}

function delete_cluster() {
    log "Deleting minikube cluster"
    minikube delete
}

function log_arc() {
    log "ARC logs"
    kubectl logs -n "${NAMESPACE}" -l "app.kubernetes.io/part-of=gha-rs-controller,app.kubernetes.io/component=controller-manager"
}

function wait_for_arc() {
    log "Waiting for ARC to be ready"
    local count=0
    while true; do
        POD_NAME=$(kubectl get pods -n "${NAMESPACE}" -l "app.kubernetes.io/part-of=gha-rs-controller,app.kubernetes.io/component=controller-manager" -o name 2>/dev/null || true)
        if [ -n "$POD_NAME" ]; then
            log "Pod found: $POD_NAME"
            break
        fi
        if [ "$count" -ge 60 ]; then
            log "Timeout waiting for controller pod with labels app.kubernetes.io/part-of=gha-rs-controller,app.kubernetes.io/component=controller-manager"
            return 1
        fi
        sleep 1
        count=$((count + 1))
    done

    kubectl wait --timeout=60s --for=condition=ready pod -n "${NAMESPACE}" -l "app.kubernetes.io/part-of=gha-rs-controller,app.kubernetes.io/component=controller-manager"
    kubectl get pod -n "${NAMESPACE}" -l "app.kubernetes.io/part-of=gha-rs-controller,app.kubernetes.io/component=controller-manager"
    kubectl describe deployment -n "${NAMESPACE}" -l "app.kubernetes.io/part-of=gha-rs-controller,app.kubernetes.io/component=controller-manager"
}

function wait_for_scale_set() {
    local count=0
    while true; do
        POD_NAME=$(kubectl get pods -n "${NAMESPACE}" -l "actions.github.com/scale-set-name=${NAME}" -o name)
        if [ -n "$POD_NAME" ]; then
            log "Pod found: ${POD_NAME}"
            break
        fi

        if [ "$count" -ge "${SCALE_SET_POD_TIMEOUT:-120}" ]; then
            log "Timeout waiting for listener pod with label actions.github.com/scale-set-name=${NAME}"
            return 1
        fi

        sleep 1
        count=$((count + 1))
    done
    kubectl wait --timeout="${SCALE_SET_READY_TIMEOUT:-120s}" --for=condition=ready pod -n "${NAMESPACE}" -l "actions.github.com/scale-set-name=${NAME}"
    kubectl get pod -n "${NAMESPACE}" -l "actions.github.com/scale-set-name=${NAME}"
}

function cleanup_scale_set() {
    log "Uninstalling Helm release ${INSTALLATION_NAME}"
    helm uninstall "${INSTALLATION_NAME}" --namespace "${NAMESPACE}" --debug

    log "Waiting for autoscaling runner sets to be deleted"
    kubectl wait --timeout="${SCALE_SET_CLEANUP_TIMEOUT:-120s}" --for=delete autoscalingrunnersets -n "${NAMESPACE}" -l app.kubernetes.io/instance="${INSTALLATION_NAME}"
}

function print_results() {
    local failed=("$@")

    if [[ "${#failed[@]}" -ne 0 ]]; then
        echo "----------------------------------"
        echo "The following tests failed:"
        for test in "${failed[@]}"; do
            echo "  - ${test}"
        done
        return 1
    else
        echo "----------------------------------"
        echo "All tests passed!"
    fi
}

function extract_run_id() {
    local workflow_output="$1"
    local run_id

    run_id="$(printf '%s\n' "${workflow_output}" | awk '/^[[:space:]]*[0-9]+[[:space:]]*$/ { gsub(/[[:space:]]/, ""); print; exit }')"
    if [[ -z "${run_id}" ]]; then
        run_id="$(printf '%s\n' "${workflow_output}" | awk 'match($0, /actions\/runs\/[0-9]+/) { run=substr($0, RSTART, RLENGTH); sub(/^actions\/runs\//, "", run); print run; exit }')"
    fi

    if [[ -z "${run_id}" ]]; then
        log "Failed to extract run id from output: ${workflow_output}"
        return 1
    fi

    printf '%s\n' "${run_id}"
}

function start_workflow() {
    local repo
    repo="${TARGET_ORG}/${TARGET_REPO}"

    # Pick a ref for workflow dispatch. Default to the repo's default branch.
    if [[ -z "${WORKFLOW_REF:-}" ]]; then
        WORKFLOW_REF="$(gh repo view -R "${repo}" --json defaultBranchRef --jq '.defaultBranchRef.name' 2>/dev/null || true)"
        WORKFLOW_REF="${WORKFLOW_REF:-main}"
    fi

    local workflow_query
    workflow_query="${WORKFLOW_FILE}"

    local workflow_id
    if [[ "${workflow_query}" =~ ^[0-9]+$ ]]; then
        workflow_id="${workflow_query}"
    else
        local q_escaped
        q_escaped="${workflow_query//\"/\\\"}"
        workflow_id="$(gh workflow list -R "${repo}" --limit 200 --json id,name,path --jq ".[] | select((.path | endswith(\"${q_escaped}\")) or (.name == \"${q_escaped}\")) | .id" 2>/dev/null | head -n1)"

        if [[ -z "${workflow_id}" ]]; then
            # Common mismatch: .yml vs .yaml
            if [[ "${workflow_query}" == *.yaml ]]; then
                q_escaped="${workflow_query%.yaml}.yml"
            elif [[ "${workflow_query}" == *.yml ]]; then
                q_escaped="${workflow_query%.yml}.yaml"
            else
                q_escaped=""
            fi

            if [[ -n "${q_escaped}" ]]; then
                local q2_escaped
                q2_escaped="${q_escaped//\"/\\\"}"
                workflow_id="$(gh workflow list -R "${repo}" --limit 200 --json id,name,path --jq ".[] | select((.path | endswith(\"${q2_escaped}\")) or (.name == \"${q2_escaped}\")) | .id" 2>/dev/null | head -n1)"
                if [[ -n "${workflow_id}" ]]; then
                    WORKFLOW_FILE="${q_escaped}"
                fi
            fi
        fi
    fi

    if [[ -z "${workflow_id}" ]]; then
        log "Workflow not found in ${repo}: ${WORKFLOW_FILE}" >&2
        log "Available workflows in ${repo}:" >&2
        gh workflow list -R "${repo}" --limit 50 || true
        log "Hint: set TARGET_ORG/TARGET_REPO to a repo that contains the workflow on its default branch, or set WORKFLOW_FILE to a valid workflow name/id/filename." >&2
        return 1
    fi

    log "Resolved workflow id: ${workflow_id} (ref: ${WORKFLOW_REF})"

    local queue_time
    queue_time="$(date -u +%FT%TZ)"

    log "Running workflow ${WORKFLOW_FILE}"
    local workflow_output
    workflow_output="$(gh workflow run -R "${repo}" "${workflow_id}" --ref "${WORKFLOW_REF}" -f arc_name="${SCALE_SET_NAME}")" || return 1
    if [[ -n "${workflow_output}" ]]; then
        log "${workflow_output}"
    fi

    log "Waiting for run to start"
    local count=0
    local run_id=
    local run_id_output=
    local run_start_interval=5
    local run_start_max=$(( "${RUN_START_TIMEOUT:-180}" / run_start_interval ))
    while true; do
        if [[ "${count}" -ge "${run_start_max}" ]]; then
            log "Timeout waiting for run to start"
            return 1
        fi
        run_id_output=$(gh run list -R "${repo}" --workflow "${workflow_id}" --created ">${queue_time}" --json "name,databaseId" --jq ".[] | select(.name | contains(\"${SCALE_SET_NAME}\")) | .databaseId")
        if [[ -n "${run_id_output}" ]]; then
            run_id=$(extract_run_id "${run_id_output}" || true)
        fi
        log "Run ID: ${run_id}"
        if [ -n "$run_id" ]; then
            log "Run found!"
            break
        fi

        log "Run not found yet, waiting ${run_start_interval} seconds"
        sleep "${run_start_interval}"
        count=$((count + 1))
    done

    echo "${run_id}"
}

function wait_for_run_completion() {
    local run_id="$1"
    local repo="${TARGET_ORG}/${TARGET_REPO}"

    log "Waiting for run ${run_id} to complete"
    gh run watch "${run_id}" -R "${repo}" --exit-status &>/dev/null
    local status=$?
    if [[ "${status}" -ne 0 ]]; then
        log "Run failed with exit code ${status}"
        return 1
    fi

    log "Run completed successfully"
}

function run_workflow() {
    local run_id
    if ! run_id=$(start_workflow); then
        log "Failed to start workflow"
        return 1
    fi
    wait_for_run_completion "${run_id}"
}

function retry() {
    local retries=$1
    shift
    local delay=$1
    shift
    local n=1

    until "$@"; do
        if [[ $n -ge $retries ]]; then
            log "Attempt $n failed! No more retries left."
            return 1
        else
            log "Attempt $n failed! Retrying in $delay seconds..."
            sleep "$delay"
            n=$((n + 1))
        fi
    done
}

function install_openebs() {
    log "Install openebs/dynamic-localpv-provisioner"
    helm repo add openebs https://openebs.github.io/openebs
    helm repo update
    helm install openebs openebs/openebs -n openebs --create-namespace
}
