#!/bin/bash

set -euo pipefail

DREHER_LIST_NAME="dreher-gmbh.list"
DREHER_LIST_PATH="/etc/apt/sources.list.d/$DREHER_LIST_NAME"
DREHER_LIST_KEY_PATH="/usr/share/keyrings/dreher-gmbh-key.asc"

DREHER_APT_OPTIONS="-o Dir::Etc::sourcelist=\"sources.list.d/$DREHER_LIST_NAME\" -o Dir::Etc::sourceparts=\"-\" -o APT::Get::List-Cleanup=\"0\""

DREHER_SHEBANG="#!/usr/bin/bash"
DREHER_LINE_TEMPL="[signed-by=$DREHER_LIST_KEY_PATH] http://repo.dreher.at/deb/"


error_handler() {
    local exit_code="$?"
    if [[ "$-" == *e* ]]; then
        echo "Das Skript $0 wurde durch einen Fehler in Zeile $1 mit dem Fehlercode $exit_code beendet" >&2
        exit $exit_code
    fi
}

trap 'error_handler $LINENO' ERR

print_err() {
    local func_name
    set +u
    local idx="$2"
    set -u
    if [ -z "$idx" ]; then
        func_name="${FUNCNAME[1]}"
    else
        func_name="${FUNCNAME["$idx"]}"
    fi
    echo "[${func_name#do_}] $1" >&2
}

get_codename() {
    local PREFIX="VERSION_CODENAME="
    local line="$(grep -e "^$PREFIX" -m 1 /etc/os-release)"
    if [ -z "$line" ]; then
        print_err "Distro-Codename konnte nicht ermittelt werden" 2
        exit 1
    fi
    echo "${line#"$PREFIX"}"
}

get_apt_list_file() {
    if [ ! -f $1 ]; then
        if [[ $1 =~ $".list"$ ]]; then
            list_file="/etc/apt/sources.list.d/$1"
        else
            list_file="/etc/apt/sources.list.d/$1.list"
        fi
        if [ ! -f "$list_file" ]; then
            print_err "Es wurde keine .list-Datei für '$1' gefunden" 2
            exit 1
        fi
    else
        list_file="$1"
    fi
}

check_list_file_exists() {
    if [ ! -f "$DREHER_LIST_PATH" ]; then
        print_err "Datei $DREHER_LIST_PATH existiert nicht" 2
        exit 1
    fi
}

check_root() {
    if [ "$EUID" -ne 0 ]; then
        print_err "Diese Funktion muss als root (z.B. mit sudo) ausgeführt werden" 2
        exit 1
    fi
}

check_arg_count() {
    local setpoint="$1"
    local actual="$2"
    local info=""
    set +u
    if [ -n "$3" ]; then
        info=" ($3)"
    fi
    set -u
    if [ "$setpoint" -eq 0 ] && [ "$actual" -ne 0 ]; then
        print_err "Diese Funktion akzeptiert keine Argumente$info" 2
        exit 1
    elif [ "$setpoint" -eq 1 ] && [ "$actual" -ne 1 ]; then
        print_err "Diese Funktion akzeptiert genau ein Argument$info" 2
        exit 1
    elif [ "$setpoint" -ne "$actual" ]; then
        print_err "Diese Funktion akzepiert genau $setpoint Argumente$info" 2
        exit 1
    fi
}

exit_if_no_int() {
    if ! [[ $1 =~ ^-?[0-9]+$ ]]; then
        echo "$1 ist keine Zahl."
        exit 1
    fi
}

write_apt_list_file() {
    exit_if_no_int $1
    local with_source=false
    local enable_testing=$2
    local line
    local codename="$(get_codename)"
    if [ $1 -lt 0 ]; then
        if ! grep -q "^#.*deb-src" $DREHER_LIST_PATH; then
            with_source=true
        fi
    elif [ $1 -gt 0 ]; then
        with_source=true
    fi
    if $enable_testing; then
        line="$DREHER_LINE_TEMPL $codename main testing"
    else
        line="$DREHER_LINE_TEMPL $codename main"
    fi
    echo "# Diese Datei wurde von '$(basename "$0")' erstellt" > "$DREHER_LIST_PATH"
    echo "deb $line" >> "$DREHER_LIST_PATH"
    if $with_source; then
        echo "deb-src $line" >> "$DREHER_LIST_PATH"
    else
        echo "# deb-src $line" >> "$DREHER_LIST_PATH"
    fi
}

cmds=("get_apt_list_info")
do_get_apt_list_info() {
    check_arg_count 1 $# "Dateiname"
    get_apt_list_file "$1"
    local line=$(awk '/^deb(\s+\[[^]]+\])?\s+\S+\s+\S+/ {
        line = $0
        # entferne die eckigen Klammern (falls vorhanden)
        gsub(/\[[^]]+\]?\s+/, "", line)
        # entferne das 'deb' am Anfang
        gsub(/^deb\s*/, "", line)
        # trenne ein eventuelles Kommentar zum Schluss ab
        gsub(/#.*$/, "", line)
        print line
    }' "$list_file")
    if [ $? -ne 0 ]; then
        print_err "Beim Parsen der Datei '$1' gab es einen Fehler"
        exit 1
    fi
    if [ "$(echo "$line" | wc -w)" -lt 3 ]; then
        print_err "Beim Parsen der Datei '$1' wurden weniger als 3 Wörter erkannt (URL, Codename, Komponenten)"
        exit 1
    fi
    echo "$line"
}

cmds+=("get_apt_list_info_all")
do_get_apt_list_info_all() {
    check_arg_count 0 $#
    for file in /etc/apt/sources.list.d/*.list; do
        do_get_apt_list_info "$file"
    done
}

cmds+=("get_apt_list_components")
do_get_apt_list_components() {
    check_arg_count 1 $# "Dateiname"
    get_apt_list_file "$1"
    info=$(do_get_apt_list_info "$list_file")
    echo $(echo "$info" | cut -d' ' -f3-)
}

cmds+=("get_info")
do_get_info() {
    check_arg_count 0 $#
    do_get_apt_list_info "$DREHER_LIST_PATH"
}

cmds+=("get_components")
do_get_components() {
    check_arg_count 0 $#
    do_get_apt_list_components "$DREHER_LIST_PATH"
}

cmds+=("get_codename")
do_get_codename() {
    check_arg_count 0 $#
    get_codename
}

cmds+=("update")
do_update() {
    check_root
    check_arg_count 0 $#
    eval "apt update $DREHER_APT_OPTIONS"
    do_upgradable
}

cmds+=("upgrade")
do_upgrade() {
    check_root
    check_arg_count 0 $#
    eval "apt -y upgrade $DREHER_APT_OPTIONS"
}

cmds+=("is_testing")
do_is_testing() {
    check_arg_count 0 $#
    for comp in $(do_get_components); do
        if [ "$comp" == "testing" ]; then
            # "'testing'-Komponente des Dreher-GmbH-Repos ist aktiviert"
            echo "1"
            exit 0
        fi
    done
    # "'testing'-Komponente des Dreher-GmbH-Repos ist deaktiviert"
    echo "0"
}

cmds+=("enable_testing")
do_enable_testing() {
    check_root
    check_arg_count 1 $# "1: enable deb-src; 0: disable deb-src; -1: nach Möglichkeit beibehalten, ansonsten disable deb-src"
    check_list_file_exists
    write_apt_list_file "$1" true
    do_update
}

cmds+=("disable_testing")
do_disable_testing() {
    check_root
    check_arg_count 1 $# "1: enable deb-src; 0: disable deb-src; -1: nach Möglichkeit beibehalten, ansonsten disable deb-src"
    check_list_file_exists
    write_apt_list_file "$1" false
    do_update
}

cmds+=("upgradable")
do_upgradable() {
    check_arg_count 0 $#
    local cmd="apt list --upgradable $DREHER_APT_OPTIONS"
    eval "$cmd"
    local output="$(eval "$cmd")"
    local count="$(echo "$output" | grep -P "^\S+/\S+\s+" | wc -l)"
    if [ "$count" -eq 0 ]; then
        echo "Es sind keine Dreher-GmbH-Updates verfügbar"
    elif [ "$count" -eq 1 ]; then
        echo "Es kann ein Dreher-GmbH-Paket aktualisiert werden"
    else
        echo "Es können $count Dreher-GmbH-Pakete aktualisiert werden"
    fi
}

cmds+=("upgradable_parsed_all")
do_upgradable_parsed_all() {
    check_arg_count 0 $#
    eval "apt list --upgradable" 2> /dev/null | gawk \
    'match($0, /^(\S+)\/\S+\s+(\S+)\s+.*: (\S+)\]$/, groups) {
        print groups[1] " " groups[2] " " groups[3]
    }'
}

cmds+=("upgradable_parsed")
do_upgradable_parsed() {
    check_arg_count 0 $#
    eval "apt list --upgradable $DREHER_APT_OPTIONS" 2> /dev/null | gawk \
    'match($0, /^(\S+)\/\S+\s+(\S+)\s+.*: (\S+)\]$/, groups) {
        print groups[1] " " groups[2] " " groups[3]
    }'
}

cmds+=("upgradable_count")
do_upgradable_count() {
    check_arg_count 0 $#
    local output=$(eval "apt list --upgradable $DREHER_APT_OPTIONS" 2> /dev/null)
    echo "$output" | grep -P "^\S+/\S+\s+" | wc -l
}

cmds+=("get_apt_vars_for_backend")
do_get_apt_vars_for_backend() {
    check_arg_count 0 $#
    echo "$DREHER_LIST_PATH"
    echo "$DREHER_APT_OPTIONS"
}

cmds+=("set_gpio_poweroff")
do_set_gpio_poweroff() {
    check_root
    check_arg_count 2 $# "GPIO-Pin, Active-Low 0/1"
    exit_if_no_int $1
    if [[ "$2" != "0" && "$2" != "1" ]]; then
        print_err "Active-Low (Argument 2) muss 0 oder 1 sein"
        exit 1
    fi

    local config_file="/boot/config.txt"
    local line_start="dtoverlay=gpio-poweroff,"
    local line_target="${line_start}gpiopin=$1,active_low=$2"

    if grep -q "^$line_target$" $config_file; then
        echo "Zeile in $config_file ist bereits $line_target"
    elif grep -q "^$line_start" $config_file; then
        echo "Zeile in $config_file wird geändert zu $line_target ..."
        sed -i "s/^${line_start}.*/$line_target/g" $config_file
    else
        echo "Zeile $line_target wird an $config_file angehängt ..."
        echo "$line_target" >> $config_file
    fi
}

cmds+=("list_commands")
do_list_commands() {
    sorted_cmds=($(printf '%s\n' "${cmds[@]}" | sort))
    for cmd in "${sorted_cmds[@]}"; do
        echo "$cmd"
    done
}


if [ $# -ge 1 ]; then
    original_cmd="$1"
    func_name="do_$1"
    shift
    if declare -f "$func_name" > /dev/null; then
        "$func_name" "$@"
        exit $?
    else
        # print_err "Es gibt kein Kommando '$original_cmd'" >&2
        print_err "Es gibt kein Kommando '$original_cmd'"
        exit 1
    fi
else
    # print_err "Es muss mindestens ein Argument (Kommando) übergeben werden" >&2
    print_err "Es muss mindestens ein Argument (Kommando) übergeben werden"
    exit 1
fi
