#!/bin/bash -u

./verify-docker-versions

# Detect docker-compose command
DOCKER_COMPOSE_COMMAND=$(docker compose version &>/dev/null && echo 'docker compose' || echo 'docker-compose')
# For the integration tests the name of the docker-compose project is generated
# by pytest. For users, we use the folder name, but strip out a few characters
# that aren't allowed.
DOCKER_COMPOSE_PROJECT_NAME=${DOCKER_COMPOSE_PROJECT_NAME:-$(tr -d ".-" <<<${PWD##*/})}

EXTRA_FILES=""

DEMO_FILES="-f docker-compose.yml -f docker-compose.storage.minio.yml -f docker-compose.demo.yml"
CLIENT_FILES="-f docker-compose.client.yml -f docker-compose.client.demo.yml"
ENTERPRISE_FILES="-f docker-compose.enterprise.yml -f docker-compose.testing.enterprise.yml"
ENTERPRISE_CLIENT_FILES="-f docker-compose.monitor-client.commercial.yml -f docker-compose.client.demo.yml"

CLIENT=0
ENTERPRISE=0
RUN_UP=0
UPLOAD_ARTIFACT=1
PRINT_LOGIN_INFO=0
PRINT_USER_EXISTS=0
RETRY_LIMIT=30

declare -a ARGS

usage() {
    cat <<EOF
$(basename $0) [options] docker-options

--client    Enable emulated client

All other arguments passed to this command are passed directly to
docker-compose, if you want to run the demo, run:

'$(basename $0) up'

To expose the server over HTTP by disabling SSL, add the following to
your docker-compose options:

'-f docker-compose.no-ssl.yml'
EOF
}

parse_args() {
    if [[ "$#" -eq 0 ]] || [[ "$1" = "-h" ]] || [[ "$1" = "--help" ]]; then
        usage
        exit 1
    fi

    while [[ -n "$1" ]]; do
        if [[ "$1" = "--no-client" ]]; then
            echo "--no-client argument is deprecated. Client is now disabled by default and can be enabled with --client"
        elif [[ "$1" = "--client" ]]; then
            CLIENT=1
            echo "-- enabling client container"
        elif [[ "$1" = "-p" ]] || [[ "$1" = "--project-name" ]]; then
            shift
            DOCKER_COMPOSE_PROJECT_NAME="$1"
        elif [[ "$1" = "--kvm" ]]; then
            echo "--kvm argument is deprecated. KVM will be enabled automatically if available"
        elif [[ "$1" = "--enterprise-testing" ]]; then
            # Undocumented flag, we use this for internal testing.
            EXTRA_FILES="$EXTRA_FILES $ENTERPRISE_FILES"
            ENTERPRISE=1
        else
            break
        fi
        shift
    done

    local EXTRA_FILES_NEXT=0
    for i in "$@"; do
        case $i in
            down|rm|stop)
                # If the argument is either "down" or "rm", enable the client so
                # that it gets cleaned up, no matter if `--client` is passed or
                # not.
                CLIENT=1
                ;;
            up)
                RUN_UP=1
                ;;
            -f=*|--file=*)
                EXTRA_FILES="$EXTRA_FILES $i"
                ;;
            -f|--file)
                EXTRA_FILES="$EXTRA_FILES $i"
                EXTRA_FILES_NEXT=1
                ;;
            *)
                if [[ $EXTRA_FILES_NEXT -eq 1 ]]; then
                    EXTRA_FILES="$EXTRA_FILES $i"
                    EXTRA_FILES_NEXT=0
                fi
                ;;
        esac
    done

    ARGS=($@)
}

check_tools() {
    # The demo environment has some external dependencies upon: curl, jq
    hash curl 2>/dev/null || { echo >&2 "The demo script requires the 'curl' tool to be available. Aborting."; exit 1; }
    hash jq 2>/dev/null || { echo >&2 "The demo script requires the 'jq' tool to be available. Aborting."; exit 1; }
}

enterprise_client_early_handling() {
    if [[ $CLIENT -eq 1 ]]; then
        if [[ $ENTERPRISE -eq 0 ]]; then
            # For Open Source, we can add the client container immediately.
            EXTRA_FILES="$EXTRA_FILES $CLIENT_FILES"
        elif [[ $RUN_UP -eq 0 ]]; then
            # For Enterprise, when RUN_UP, we don't add the container to
            # take special care and fetch the tenant token first.
            EXTRA_FILES="$EXTRA_FILES $ENTERPRISE_CLIENT_FILES"
        fi
    fi
}

download_demo_artifact() {
    # Check if the demo-Artifact has been downloaded,
    # or if there exists a newer one in storage.
    DEMO_ARTIFACT_NAME="mender-demo-artifact.mender"
    curl -q -sz mender-demo-artifact.mender -o mender-demo-artifact.mender https://dgsbl4vditpls.cloudfront.net/${DEMO_ARTIFACT_NAME}

    retval=$?
    if [[ $retval -ne 0 ]]; then
        echo "Failed to download the demo Artifact"
        exit $retval
    fi
}

platform_dependent_setup() {
    if [[ "$OSTYPE" == "darwin"* ]]; then
        ARTIFACT_SIZE_BYTES=$(stat -f %z ${DEMO_ARTIFACT_NAME}) # BSD is not GNU -_-
        export GATEWAY_IP=$(ifconfig $(netstat -rn | grep default | head -1 | awk '{print($NF)}') inet | grep -F 'inet '| sed -e 's/.*inet \([^ ]*\) .*/\1/')
    else
        ARTIFACT_SIZE_BYTES=$(stat -c %s ${DEMO_ARTIFACT_NAME})
        export GATEWAY_IP=$(ip route get 1 | awk '{print $7;exit}')
    fi
}

pull_docker_containers() {
    # speed up first start by pulling containers in parallel
    docker images | grep -q 'mendersoftware/deployments'

    if [[ "$?" -eq 1 ]]; then
        compose_args=""
        docker_compose_output=$(${DOCKER_COMPOSE_COMMAND} pull -h)

        # If --no-parallel option exists, it means that docker-compose is
        # running a version where --parallel is default and will warn about
        # deprecated option if used.
        #
        # This behavior was changed in version docker-compose 1.21.0
        echo "$docker_compose_output" | grep -q -- '--no-parallel'
        if [[ "$?" -eq 1 ]]; then
            compose_args="--parallel"
        fi

        ${DOCKER_COMPOSE_COMMAND} pull ${compose_args}
    fi
}

env_setup() {
    # Pass this value on to the GUI container as an env variable
    export INTEGRATION_VERSION=$(git describe --tags --abbrev=0)
    # Parse the Mender-Artifact version used from the other-components.yml file's image tag
    export MENDER_ARTIFACT_VERSION=$(awk -F':' '/mendersoftware\/mender-artifact/ {print $3}' other-components.yml)
    # Parse the mender version from docker-compose.yml mender image's tag
    export MENDER_VERSION=$(awk -F':'mender- '/mendersoftware\/mender-client/ {print $2}' docker-compose.client.yml)
    export MENDER_DEB_PACKAGE_VERSION=$MENDER_VERSION

    MENDER_SERVER_URI="https://localhost"
    # use http when providing no-ssl config
    if [[ "${ARGS[*]}" == *"-f docker-compose.no-ssl.yml"* ]]
    then
        MENDER_SERVER_URI="http://localhost"
    fi

    USER='mender-demo@example.com'
    PASSWORD=$(hexdump -n 8 -e '"%X"' < /dev/urandom | cut -c1-12)
}

run_non_up_commands() {
    if [[ $RUN_UP -eq 0 ]]; then
        # exec steals the shell, so unless docker-compose is not found,
        # exit 1 will never happen.
        run_compose_command "${ARGS[@]}"
        exit 1
    fi
}

# Make sure that the demo environment is brought down on SIGINT
exitfunc() {
    retval=$(run_compose_command stop)
    exit $retval
}

start_server() {
    echo "Starting the Mender demo environment..."

    run_compose_command "${ARGS[@]}" -d

    local retval=$?
    if [[ $retval -ne 0 ]]; then
        echo "Failed to start docker compose"
        exit $retval
    fi

    # Probe healthcheck endpoints until all services are ready
    local RETRIES=0
    echo "Waiting for services to become ready..."
    # For each container in docker-compose project with a healthcheck docker label
    IFS= docker ps -a \
        --filter "label=com.docker.compose.project=${DOCKER_COMPOSE_PROJECT_NAME}" \
        --filter "label=mender.healthcheck.path" \
        --format '{{.ID}} {{.Names}} {{.Label "mender.healthcheck.path"}}' | \
    while read -r container_id container_name health_path; do
        local container_ip
        # Get container ip and status
        container_ip=$(docker container inspect $container_id \
            --format "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}")
        # At this point all containers should be running, if ip is empty
        # that means the container exited.
        if [ $? != 0 ] || [ -z "$container_ip" ]; then
            echo "Failed to obtain address of container ${container_name}."
            exit 1
        elif [ -n "$container_ip" ]; then
            break
        fi

        while :
        do
            curl --silent -k -X GET \
                    --fail \
                    --connect-timeout 5 \
                    "http://$container_ip:8080/${health_path#/}"
            case $retval in
                0)  break ;;
                *) ;;
            esac
            if [[ $RETRIES -ge $RETRY_LIMIT ]]; then
                echo "Retried $RETRIES times without success. Giving up."
                exit 1
            fi
            RETRIES=$((RETRIES+1))
            sleep 1
        done
    done
}

create_user() {
    echo "Creating a new user..."

    if [[ $ENTERPRISE -eq 1 ]]; then
        TENANT_ID=$(docker exec \
                $(run_compose_command ps -q mender-tenantadm) \
                /usr/bin/tenantadm create-org \
                --name=DemoOrganization \
                --addon configure \
                --addon monitor \
                --addon troubleshoot \
                --username=${USER} \
                --password=${PASSWORD})
        retval=$?
        EXISTS_ERROR=1
        if [[ $retval -eq 0 ]]; then
            TENANT_ID=$(echo "$TENANT_ID" | tr -d '\r')
        else
            TENANT_ID=
        fi
    else
        docker exec \
                $(run_compose_command ps -q mender-useradm) \
                /usr/bin/useradm create-user \
                --username=${USER} \
                --password=${PASSWORD} \
                > /dev/null
        retval=$?
        EXISTS_ERROR=5
    fi
    if [[ $retval -eq 0 ]]; then
        PRINT_LOGIN_INFO=1
    elif [[ $retval -eq $EXISTS_ERROR ]]; then
        # If the user exists, skip uploading the Artifact
        UPLOAD_ARTIFACT=0
        PRINT_USER_EXISTS=1
    else
        echo "docker exec error: " $retval
        exit $retval
    fi
}

maybe_launch_enterprise_client() {
    if [[ $ENTERPRISE -eq 1 ]] && [[ $CLIENT -eq 1 ]]; then
        if [[ $(run_compose_command ps -q mender-client | wc -l) -gt 0 ]]; then
            # If already launched, we don't need to do anything.
            :
        elif [[ -n "$TENANT_ID" ]]; then
            TENANT_TOKEN=$(docker exec \
                    $(run_compose_command ps -q mender-tenantadm) \
                    /usr/bin/tenantadm get-tenant \
                    --id $TENANT_ID \
                    | jq -r .tenant_token)
            # Now that we have the tenant token we can enable the client.
            EXTRA_FILES="$EXTRA_FILES $ENTERPRISE_CLIENT_FILES"
            TENANT_TOKEN=$TENANT_TOKEN run_compose_command "${ARGS[@]}" -d mender-client
        else
            echo "WARNING: Ignoring request to launch the Mender client."
            echo "In Enterprise mode, the client can only be launched the first time the server"
            echo "is started, when the first user is created. If you wish to start from scratch,"
            echo "replace \`up\` with \`down -v\` first to reset, then rerun."
        fi
    fi
}

maybe_upload_artifact() {
    if [[ $UPLOAD_ARTIFACT -eq 1 ]]; then

        local RETRIES=0
        local retval=0
        local JWT=
        until [[ -n "$JWT" ]]; do
            JWT=$(curl --silent -k -X POST -u ${USER}:${PASSWORD} \
                    --fail \
                    --connect-timeout 5 \
                    $MENDER_SERVER_URI/api/management/v1/useradm/auth/login)
            retval=$?
            if [[ $retval -ne 0 ]]; then
                echo "Failed to get the 'JWT' token from the useradm service."
                echo "This is needed in order to upload the demo Artifact."
                echo "curl exit code: " $retval
                echo "Retrying in 5..."
            fi
            if [[ $RETRIES -ge $RETRY_LIMIT ]]; then
                echo "Retried $RETRIES times without success. Giving up."
                exit 1
            fi
            RETRIES=$((RETRIES+1))
            sleep 5
        done

        local cout=
        RETRIES=0
        while :
        do
            cout=$(curl --silent -k -X POST \
                    --fail \
                    --show-error \
                    --connect-timeout 5 \
                    --header "Authorization: Bearer ${JWT}" \
                    --form "size=${ARTIFACT_SIZE_BYTES}" \
                    --form "artifact=@${DEMO_ARTIFACT_NAME}" \
                    $MENDER_SERVER_URI/api/management/v1/deployments/artifacts)
            retval=$?
            if [[ $retval -ne 0 ]]; then
                echo "Failed to upload the Artifact to the demo server. curl error code: " $retval
                echo "Sleeping for 5 seconds before making another attempt..."
            else
                break
            fi
            if [[ $RETRIES -ge $RETRY_LIMIT ]]; then
                echo "Retried $RETRIES times without success. Giving up."
                exit 1
            fi
            RETRIES=$((RETRIES+1))
            sleep 5
        done

        local errout=$(jq '.error' <<< $cout)

        retval=$?
        if [[ $retval -ne 0 ]]; then
            echo "Failed to parse the json response from the Mender server"
            echo "Response: "
            echo $cout
            exit $retval
        fi

        case "$errout" in
            " Artifact not unique"*) ;;  # Artifact already exists on the server
            "") ;;  # Artifact uploaded to the demo server
            *) echo "Uploading the demo Artifact failed with error: " $errout
               exit 1 ;;
        esac

    fi
}

print_info() {
    if [[ $PRINT_LOGIN_INFO -eq 1 ]]; then
        echo "****************************************"
        echo
        echo "Username: ${USER}"
        echo "Login password: ${PASSWORD}"
        echo
        echo "****************************************"
        echo "Please keep the password available, it will not be cached by the login script."
    elif [[ $PRINT_USER_EXISTS -eq 1 ]]; then
        echo "The user already exists. Skipping"
        echo "If you don't remember the password, you can run '$(basename $0) down' to delete"
        echo "the old user and rerun '$(basename $0) up' to create a new one."
        echo "Please note that all data will be deleted from the old demo server."
    fi

    echo "Mender demo server ready and running in the background. Copy credentials above and log in at $MENDER_SERVER_URI"
}

wait_for_user() {
    echo "Press Enter to show the logs."
    echo "Press Ctrl-C to stop the backend and quit."
    read -se
}

follow_logs() {
    run_compose_command logs --follow
}

run_compose_command() {
    ${DOCKER_COMPOSE_COMMAND} \
            $DEMO_FILES \
            -p ${DOCKER_COMPOSE_PROJECT_NAME} \
            $EXTRA_FILES "$@"
}

#-------------------------------------------------------------------------------
#
# Start execution
#
#-------------------------------------------------------------------------------

check_tools
parse_args "$@"
enterprise_client_early_handling
download_demo_artifact
platform_dependent_setup
pull_docker_containers
env_setup
run_non_up_commands

# ------------------------------------------------------------------------------
#
#       The following code will only be run in the case ./demo up [[args]]
#
# ------------------------------------------------------------------------------
trap exitfunc SIGINT
trap exitfunc SIGTERM

start_server
create_user
maybe_launch_enterprise_client
maybe_upload_artifact
print_info
wait_for_user

# ------------------------------------------------------------------------------
#
# We will only get here if the user presses Enter.
#
# ------------------------------------------------------------------------------
follow_logs
