#!/usr/bin/env bash

###############################################################################
# The following code helps make script errors more prominent and descriptive.
###############################################################################

error_handler() {
    local -r code="$1"
    local -r line="$2"
    local -r file="$3"
    local -r red="\033[0;31m"
    local -r no_color="\033[0m"
    echo -e "${red}ERROR(${code}) on line ${line} of ${file}:${no_color}"\
         "$(head -n ${line} ${file} | tail -1 | xargs)" 1>&2
    exit "${code}"
}
trap 'error_handler $? ${LINENO} ${BASH_SOURCE}' ERR
set -uEo pipefail
if [[ $(echo $BASH_VERSION | cut -d. -f1) -lt 4 ]]; then
    error "Bash 4.0 or later is required to run these scripts."
fi

###############################################################################
# The following helpers make it more convenient to access gitlab resources
###############################################################################

GITLAB_URL=https://git.doit.wisc.edu/
GITLAB_API=api/v4/
GITLAB_COURSE=cdis/cs/courses/cs400/202505/
cgl() {
    local target=$1
    local type=${2:-GET}
    local data=${3:-}
    if [[ -n "$data" ]]; then
        data=(--header "Content-type: application/json" --data "${data}")
    else
	data=()
    fi
    curl --silent --request ${type^^} --header "PRIVATE-TOKEN: ${GITLAB_TOKEN##*:}" "${data[@]}" --url ${GITLAB_URL}${GITLAB_API}${target}
}

###############################################################################
# The following entry point either displays a usage message, or dispatches the
# requested command appropriately (after confirming the availability of needed
# software and gitlab token).
###############################################################################

cs400() {
    if [[ $# -lt 1 || $# -gt 2 ]]; then
	echo "Usage: cs400 [-v] <command>"
	echo ""
	echo "Available commands include:"
	echo "    init     configures user account to use other cs400 commands"
	echo "    get      retrieves starter folder for a specific assignment"
	echo "    submit   submits assignment from current working directory"
	echo "    check    retrieves feedback for current assignment folder"
	echo "    restart  resets contents of current assignment folder"
	echo ""
	echo "The option -v provides a more verbose display of any errors."
	echo ""
	exit 0
    fi

    # redirect extra diagnostics and error messages to VERBOSE
    VERBOSE="/dev/null"
    if [[ $(echo "$@" | grep -ic "\-v") -gt 0 ]]; then
	VERBOSE="/dev/stdout"
    fi

    # switch through command options (collecing username, token, etc as needed)
    read -p "Enter your NetID username: " NETID
    if [[ ${1,,} = init ]]; then
	init
    else
	readToken # sets GITLAB_TOKEN
	readActivityList # sets ACTIVITYLIST
	if [[ ${1,,} = get ]]; then
	    get
	else
	    confirmActivityWorkingDirectory
	    if [[ ${1,,} = submit ]]; then
		submit
	    elif [[ ${1,,} = check ]]; then
		check
	    elif [[ ${1,,} = restart ]]; then
		restart
	    fi
	fi
    fi
}

###############################################################################
# Helper methods for installing needed software, and storing gitlab token.
###############################################################################
init() {
    installJq
    installGum    
    # overwrite $USER when run with sudo, to be actual user rather than root
    if [[ $EUID -eq 0 ]]; then export USER="$SUDO_USER"; fi
    
    setupGitlabAccessToken
    readToken # sets GITLAB_TOKEN
    configLocalGitAccount
}

installJq() {
    echo "Attempting to install jq, when needed."
    if [[ ! $(command -v jq) ]]; then
	if [[ $EUID -ne 0 ]]; then
	    echo "This script requires super user privelege to install jq."
	    echo "Please try running \"sudo cs400 init\"."
	    exit
	fi
	sudo apt update -y
	sudo apt install -y jq
    fi
}

installGum() {
    echo "Attempting to install gum, when needed."
        if [[ ! $(command -v gum) ]]; then
	if [[ $EUID -ne 0 ]]; then
	    echo "This script requires super user privelege to install jq."
	    echo "Please try running \"sudo cs400 init\"."
	    exit
	fi
	sudo mkdir -p /etc/apt/keyrings
	curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
	echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
	sudo apt update -y && sudo apt install -y gum
    fi
}

setupGitlabAccessToken() {
    echo ""
    echo "Setup GitLab Access Token:"
    echo ""
    echo "* Note that your personal access token should be kept private like a password.  If you ever lose or worry that someone may have gained access to this private information: You can delete this token gitlab's web interface, and then rerun this script \"cs400 init\" to retrieve and store a new one."
    echo ""
    echo "1) Navigate your browser to https://git.doit.wisc.edu/-/user_settings/personal_access_tokens"
    echo "2) When prompted to sign in, use the \"UW-Madison NetID\" button at the bottom of this page"
    echo "3) Click the \"Add new token\" button under \"Personal Access Token\" on this page."
    echo "4) Enter your choice of token name, \"cs400-pat\" is descriptive"
    echo "5) Enter July 31, 2025 as the expiration date"
    echo "6) Click to mark every checkboxes under \"Select scopes\""
    echo "7) Click the \"Create personal access token\" button"
    echo "8) Click the \"Copy personal access token button\" button with a clipboard icon, and then paste the result below"
    echo ""
    read -p "Paste your GitLab Personal Access Token here (then press enter/return): " GITLAB_TOKEN
    if [[ $(echo "$GITLAB_TOKEN" | grep -Ec "^glpat-") != 1 ]]; then
	echo "A valid Gitlab Personal Access Token should begin with: glpat-"
	echo "Please try running this script again to enter a valid token."
	exit 1
    fi
    ACCESSFILE="$(getent passwd $USER | cut -d: -f6)/.gitlab.access"
    echo "${NETID,,}:${GITLAB_TOKEN}" > $ACCESSFILE
    chown $USER $ACCESSFILE
}

readToken() { # sets GITLAB_TOKEN
    # confirm access token is available
    if [[ ! -f "$(getent passwd $USER | cut -d: -f6)/.gitlab.access" ]]; then
	echo "No gitlab access token found, run \"cs400 init\" to create one"
	exit 1
    fi
    FILE="$(getent passwd $USER | cut -d: -f6)/.gitlab.access"
    declare -g GITLAB_TOKEN=$(cat $FILE)
    # confirm access token matches NETID entered by user
    if [[ ! ${GITLAB_TOKEN%%:*} = ${NETID,,} ]]; then	
	echo "NetID entered: $NETID does NOT match NetID stored with GitLab access token: ${GITLAB_TOKEN%%:*}.  Try running \"cs400 init\" to store your NetID along with a valid Gitlab Access Token here for the current user."
	exit 1
    fi
    # confirm Gitlab username also matches NETID entered by user
    local username=$(cgl user | jq -r ".username")
    if [[ ! ${username,,} = ${NETID,,} ]]; then
	echo "Gitlab Username: ${username,,} does not match NetID entered: ${NETID,,}"
	exit 1
    fi
}

configLocalGitAccount() {
    echo "Copying gitlab name and email here to local git profile."
    USERINFO=$(cgl user)

    if [[ $EUID -eq 0 ]]; then # work when running this script with sudo
	su $USER -c "git config --global user.name \"$(echo $USERINFO | jq -r .name)\""
	su $USER -c "git config --global user.email \"$(echo $USERINFO | jq -r .email)\""
	su $USER -c "git config --global pull.rebase false"
    else # and also when running this script without sudo
    	git config --global user.name "$(echo $USERINFO | jq .name)"
	git config --global user.email "$(echo $USERINFO | jq .email)"
	git config --global pull.rebase false
    fi
}

###############################################################################
# Read list of available activities from gitlab for the non-init commands
###############################################################################
readActivityList() { # sets ACTIVITYLIST
    STUDENTGROUP="${GITLAB_COURSE}students/$NETID"
    ENCODED_STUDENTGROUP="${STUDENTGROUP//\//%2F}"
    GROUPID=$(cgl groups/${ENCODED_STUDENTGROUP})
    if [[ $(echo $GROUPID | grep -c "id") < 1 ]]; then
	echo "Unable to access gitlab group id."
	exit 1
    else
	GROUPID=$(echo $GROUPID | jq .id)
	ACTIVITYLIST="$(cgl groups/$GROUPID/projects | jq -r '.[].name')"
    fi
}

###############################################################################
# Retrieves a copy of the student's gitlab repository for any activity
###############################################################################
get() {
    ACTIVITY=$(gum filter --prompt "Choose activity to get:" $ACTIVITYLIST)
    if [[ -d "$ACTIVITY" ]]; then
	echo "You already have a folder for this activity in your working directory.  You'll need to remove or rename this directory before another with the same name can be created here."
	exit 1
    fi
    if [[ -z "$ACTIVITY" ]]; then
	echo "You failed to select an available activity."
	exit 1
    fi
    
    URL="${GITLAB_URL}${GITLAB_COURSE}students/${NETID,,}/${ACTIVITY}.git"
    git clone ${URL/https:\/\//https:\/\/${GITLAB_TOKEN}@} > $VERBOSE 2>&1
    
    echo "You should now have a new $ACTIVITY folder."
    echo "If not, try running \"cs400 get -v\" for more information about any problems that migth have prevented this from working as expected."
}

###############################################################################
# Confirms that script is running from within a listed activity directory
###############################################################################
confirmActivityWorkingDirectory() {
    ACTIVITY="${PWD##*/}"
    if [[ $(echo "$ACTIVITYLIST" | grep -ic "^${ACTIVITY}$") != 1 ]]; then
	echo "The folder you are running this script from does not appear to correspond to a CS400 activity.  This command should be run from a folder containing one of the following namnes:" $ACTIVITYLIST
	exit 1
    fi
    if [[ ! -d "./.git" ]]; then
	echo "You can only run this script from within a directory that was previously created by running the \"cs400 get\" command."
	exit 1
    fi
}

###############################################################################
# Submits the contents fo the current directory to gitlab
###############################################################################
submit() {
    # update token in origin path in case token used to get has been deleted
    URL="${GITLAB_URL}${GITLAB_COURSE}students/${NETID,,}/${ACTIVITY}.git"
    git remote set-url origin ${URL/https:\/\//https:\/\/$(cat ~/.gitlab.access)@}

    git add -A > $VERBOSE
    git commit -m "cs400 script submission from $(TZ='America/Chicago' date)" > $VERBOSE || true
    git pull -s ours --no-edit &> $VERBOSE
    git push > $VERBOSE 2>&1
    if [[ $? -eq 0 ]]; then
	echo "Work submitted, use \"cs400 check\" to confirm submission time and check result of automated submission checks."
    else
	echo "Encountered a submission error.  Use \"cs400 submit -v\" to retry and display more details about the cause of this error."
    fi
}

###############################################################################
# Retrieves the time and submission checker feedback for most recent submission
###############################################################################
check() {
    STUDENTGROUP="${GITLAB_COURSE}students/$NETID"
    ENCODED_STUDENTGROUP="${STUDENTGROUP//\//%2F}"
    GROUPID=$(cgl groups/${ENCODED_STUDENTGROUP} | jq -r .id)
    PROJECTID=$(cgl groups/$GROUPID/projects?search=$ACTIVITY | jq ".[] | select(.name==\"${ACTIVITY}\") | .id")
    PIPEINFO=$(cgl projects/$PROJECTID/jobs | jq first)
    echo "Status of automated submission checks from most recent submission $(echo $PIPEINFO | jq -r .created_at): $(echo $PIPEINFO | jq -r .status)"
    echo ""
    echo "Note that these submission checks are NOT used for grading, but are instead to help you detect some kinds of submission failures.  For more details about any problems detected, review the \"Executing step_script stage of the job script\" section on this webpage: $(echo $PIPEINFO | jq .web_url)"
}

###############################################################################
# Restarts the assignment by returning repository to previous state
###############################################################################
restart() {
    read -p "Are you sure you want to overwrite the work in this directory: ${PWD} and restart? Enter yes or no: " CONFIRMCHOICE
    if [[ $CONFIRMCHOICE = yes ]]; then
	if [[ -f .restartSubmission ]]; then
	    ./.restartSubmission
	else
	    if git tag | grep -q restart; then
		rm -rf *
		git checkout restart . > $VERBOSE
	    else
		echo "This assignment does not have a way to be restarted."
		exit 1
	    fi
	fi
    else
	echo "Assignment restart SAFELY ABORTED."
    fi
}

############################################################################## 
# Calls the cs400 method when executing this script from the command line   
if ! (return 0 2> /dev/null); then
    cs400 "$@"
fi
############################################################################## 


