#!/bin/bash

# Declare an associative array to store the images used on each machine
declare -A MACHINE_CONTAINER_IMAGES

TEST_MACHINES=(
        "cgaltest@bonnard"
        "lrineau@cgal"
        "cgaltest@friedrich"
        "cgaltest@rubens"
)

machine_title() {
        printf '\n## %s ##\n' "$1"
}

machine_info() {
        HOST=$1
        remote_script=$(printf "export PS4='+ %s >> %s'\n" "$HOST" "$PS4")$'\n'$(
                cat <<'EOF'
source /usr/lib/os-release
printf '\n- OS: `%s`\n- kernel: `%s`\n- container implementation: `%s`\n' "$PRETTY_NAME" "$(uname -r)" "$(docker --version)"

EOF
        )
        ssh "$HOST" bash -$- -s <<<"$remote_script"
}

machine_tested_images() {
        echo
        echo '```plain'
        printf '%s\n' "${MACHINE_CONTAINER_IMAGES["$1"]}"
        echo '```'
}

docker_is_active_cmd() {
        systemctl is-active -q docker
        return $?
}
declare -xf docker_is_active_cmd

docker_cmd() {
        if docker_is_active_cmd; then
                docker "$@"
        else
                podman --url unix:/var/run/podman/podman.sock "$@"
        fi
}
declare -xf docker_cmd

list_of_containers_cmd() {
        docker_cmd ps -a --format '{{.Names}}' --filter name="CGAL-"
}
declare -xf list_of_containers_cmd

container_status_cmd() {
        docker_cmd inspect --format '{{.State.Status}}' "$1"
}
declare -xf container_status_cmd

container_human_readable_status_cmd() {
        docker_cmd ps --all --filter name="$1" --format '{{.Status}}'
}
declare -xf container_human_readable_status_cmd

dnf_check_updates_cmd() {
        if command -v dnf >/dev/null; then
                dnf --disable-plugin='*' check-upgrade --security --quiet
        else
                echo "dnf is not available"
                return 1
        fi
}
declare -xf dnf_check_updates_cmd

simplify_date_cmd() {
        date=$1
        pattern=' \+[0-9]{4} [+]?[A-Z0-9]{3,}$'
        if [[ $date =~ $pattern ]]; then
                date=${date% *}
        fi
        echo "$date"
}
declare -xf simplify_date_cmd

container_start_time_cmd() {
        simplify_date_cmd "$(docker_cmd inspect --format '{{.State.StartedAt}}' "$1")"
}
declare -xf container_start_time_cmd

container_end_time_cmd() {
        simplify_date_cmd "$(docker_cmd inspect --format '{{.State.FinishedAt}}' "$1")"
}
declare -xf container_end_time_cmd

container_running_time_cmd() {
        start_time=$(container_start_time_cmd "$1")
        end_time=$(container_end_time_cmd "$1")
        status=$(container_status_cmd "$1")
        if [ "$status" = "running" ]; then
                end_time=$(date -u '+%Y-%m-%dT%H:%M:%S.%NZ')
        fi
        secs=$(($(date -d "$end_time" +%s) - $(date -d "$start_time" +%s)))
        printf '%02dh:%02dm:%02ds\n' $((secs / 3600)) $((secs % 3600 / 60)) $((secs % 60))
}
declare -xf container_running_time_cmd

display_one_container_line_cmd() {
        printf '%s\t%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" "$5"
}
declare -xf display_one_container_line_cmd

list_cgal_test_container_cmd() {
        # docker_cmd ps -a --filter name=CGAL-
        display_one_container_line_cmd "CONTAINER" "START TIME" "END TIME" "RUNNING TIME" "STATUS"
        for container in $(list_of_containers_cmd); do
                start_time="$(container_start_time_cmd "$container")"
                end_time="$(container_end_time_cmd "$container")"
                dur=$(container_running_time_cmd "$container")
                status="$(container_status_cmd "$container") - $(container_human_readable_status_cmd "$container")"
                display_one_container_line_cmd "$container" "$start_time" "$end_time" "$dur" "$status"
        done
}
declare -xf list_cgal_test_container_cmd

display_all_exported_cmd_functions() {
        functions=$(declare -F | awk '/ -fx .*_cmd$/ {print $3}')
        for func in $functions; do
                declare -f "$func"
        done
}

machine_list_cgal_test_container() {
        printf '\n```tsv\n'
        remote_script=$(
                display_all_exported_cmd_functions
                printf "export PS4='+ %s >> %s'\n" "$1" "$PS4"
                echo list_cgal_test_container_cmd
        )
        ssh "$1" bash -$- -s <<<"$remote_script"
        printf '```\n'
}

machine_check_updates() {
        remote_script=$(
                display_all_exported_cmd_functions
                printf "export PS4='+ %s >> %s'\n" "$1" "$PS4"
                echo dnf_check_updates_cmd
        )
        ssh "$1" bash -$- -s <<<"$remote_script"
}

machine_run_command() {
        ssh "$1" bash -$- -s <<<"$2"
}

help() {
        cat <<HEREDOC
Usage: $0 [OPTION]

List the test runner machines and the containers running on them.

Output Format Options:
  --table       output in markdown table format
  --column      output in column format
  --bat         output with bat
  --plain       output in plain text

If no option is given, the script will try to use bat, then column, and finally
plain text.

Information Options:
  --images      list the images used on each machine
  --containers  list the containers running on each machine
  --info        list the OS and the container implementation on each machine
  --updates     list the available security updates on each machine
  --run CMD     run CMD on each machine using ssh

If no information option is given, the script will list all the information.
HEREDOC
}

error_out() {
        exec >&2
        echo "ERROR: $1"
        echo
        help
        exit 1
}

command -v sed >/dev/null || {
        error_out 'sed is required'
}

set_pretty_csv_to_md_table() {
        pretty_csv() (
                echo
                sed '/```/ d; /^$/ d' | pandoc -f tsv -t gfm
        )
}

set_pretty_csv_to_column() {
        pretty_csv() {
                echo
                column -t -s $'\t' -o $'\t' | sed 's/^\(```[^ ]*\) *\t.*/\1/'
        }
}

set_pretty_csv_to_column_and_bat() {
        pretty_csv() {
                echo
                column -t -s $'\t' -o $'\t' | sed 's/^\(```[^ ]*\) *\t.*/\1/' | bat --paging=never --plain -l csv
        }
}

set_pretty_csv_to_bat() {
        pretty_csv() {
                bat --tabs=50 --paging=never --plain -l csv
        }
}

set_pretty_csv_to_cat() {
        pretty_csv() {
                cat
        }
}

WHAT=()
RUN_COMMAND=

add_to_what() {
        for i in "$@"; do
                WHAT+=("$i")
        done
}

what_contains() {
        local item=$1
        for i in "${WHAT[@]}"; do
                if [[ "$i" == "$item" ]]; then
                        return 0
                fi
        done
        return 1
}

while [[ $# -gt 0 ]]; do
        case "$1" in
        --table)
                command -v pandoc >/dev/null || error_out 'pandoc is required for the option --table'
                set_pretty_csv_to_md_table
                shift
                ;;
        --column)
                command -v column >/dev/null || error_out 'column is required for the option --column'
                set_pretty_csv_to_column
                shift
                ;;
        --bat)
                command -v bat >/dev/null || error_out 'bat is required for the option --bat'
                set_pretty_csv_to_bat
                shift
                ;;
        --plain)
                set_pretty_csv_to_cat
                shift
                ;;
        --images)
                add_to_what images
                shift
                ;;
        --containers)
                add_to_what containers
                shift
                ;;
        --updates)
                add_to_what updates
                shift
                ;;
        --info)
                add_to_what info
                shift
                ;;
        --run)
                shift
                [ $# -gt 0 ] || error_out 'missing argument for option --run'
                RUN_COMMAND=$1
                add_to_what run
                shift
                ;;
        --run=*)
                RUN_COMMAND=${1#--run=}
                [ -n "$RUN_COMMAND" ] || error_out 'missing argument for option --run'
                add_to_what run
                shift
                ;;
        -h | --help)
                help >&2
                exit 0
                ;;
        *)
                error_out "Unknown option $1"
                ;;
        esac
done

if [ ${#WHAT[@]} -eq 0 ]; then
        add_to_what info images containers updates
fi

STDOUT_IS_A_TTY=
if <&1 tty -s; then
        STDOUT_IS_A_TTY=1
fi

BAT=
[ -n "$STDOUT_IS_A_TTY" ] && BAT=$(command -v bat)

COLUMN=$(command -v column)

PANDOC=$(command -v pandoc)
if ! declare -f pretty_csv >/dev/null; then
        if [ -n "$BAT" ]; then
                if [ -n "$COLUMN" ]; then
                        set_pretty_csv_to_column_and_bat
                else
                        set_pretty_csv_to_bat
                fi
        elif [ -n "$PANDOC" ]; then
                set_pretty_csv_to_md_table
        elif [ -n "$COLUMN" ]; then
                set_pretty_csv_to_column
        else
                set_pretty_csv_to_cat
        fi
fi

if what_contains images; then
        ERROR_MACHINES=""
        for machine in "${TEST_MACHINES[@]}"; do
                USER=${machine%@*}
                HOST=${machine#*@}
                # shellcheck disable=SC2029
                MACHINE_CONTAINER_IMAGES[$machine]=$(ssh "$HOST" cat "/home/$USER/.config/CGAL/test_cgal_docker_images") || {
                        ERROR_MACHINES="$ERROR_MACHINES $machine"
                }
        done
        if [ -n "$ERROR_MACHINES" ]; then
                printf '\n> %s\n> %s\n' '[!CAUTION]' 'ERROR:'
                for machine in $ERROR_MACHINES; do
                        USER=${machine%@*}
                        HOST=${machine#*@}
                        # shellcheck disable=SC2016
                        printf '> - ERROR: cannot read file `/home/%s/.config/CGAL/test_cgal_docker_images` on ssh host `%s`\n' "$USER" "$HOST"
                done
                exit 1
        fi
fi
cat <<HEREDOC
# Test runner machines #

The following machines are used to run the tests:
HEREDOC

for machine in "${TEST_MACHINES[@]}"; do
        USER=${machine%@*}
        HOST=${machine#*@}
        machine_title "$machine"
        if what_contains info; then
                machine_info "$HOST"
        fi
        if what_contains images; then
                printf '\nTested images:\n'
                machine_tested_images "$machine"
        fi
        if what_contains containers; then
                printf '\nCGAL test containers:\n'
                machine_list_cgal_test_container "$HOST" "$USER" | pretty_csv
        fi
        if what_contains updates; then
                printf '\nAvailable security updates:\n'
                machine_check_updates "$HOST" "$USER"
        fi
        if what_contains run; then
                printf '\nOutput of command `%s`:\n' "$RUN_COMMAND"
                printf '\n```plain\n'
                machine_run_command "$HOST" "$RUN_COMMAND"
                printf '```\n'
        fi
done
