# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: Copyright 2023-2024 SUSE LLC
# SPDX-FileCopyrightText: Copyright 2023-2024 Richard Brown

. /usr/lib/tik/lib/cenity

log(){
    if $logging; then
        echo "[${tik_module}][$(date +"%Y%m%d-%T")][LOG] $*" 1>&2
    fi
}

warn() {
    echo "[${tik_module}][$(date +"%Y%m%d-%T")][WARN] $*" 1>&2
    d --warning --text="$*"
}

error() {
    echo "[${tik_module}][$(date +"%Y%m%d-%T")][ERROR] $*" 1>&2
    d --error --text="$*"
    exit 1
}

d(){
    while true
    do
        retval=0
        if $gui; then
            result="$(zenity "$@")" || retval=$?
            log "[zenity][${retval}][${result}] $@"
        else
            cenity result "$@" || retval=$?
            log "[cenity][${retval}][${result}] $@"
        fi
        case $retval in
            0)
                return 0
            ;;
            1|255)
                if $gui; then
                    zenity --question --text="Do you really want to quit?" && exit 1
                else
                    cenity result --question --text="Do you really want to quit?" && exit 1
                fi
            ;;
        esac
    done
}

d_opt(){
    retval=0
    if $gui; then
        result="$(zenity "$@")" || retval=$?
        log "[zenity][${retval}][${result}] $@"
    else
        cenity result "$@" || retval=$?
        log "[cenity][${retval}][${result}] $@"
    fi
    return $retval
}

# variant of privileged run (prun) function that doesn't require the pkexec call to return 0
prun-opt() {
    if [ "${debug}" == "1" ]; then
        log "[pkexec-noexec] $@"
    else
        retval=0
        pkexec "$@"
        retval=$?
        log "[pkexec][${retval}] $@"
    fi
}

# Most commonly used prun function, which requires the called command to work
prun() {
    prun-opt "$@"
    if [ "${retval}" != "0" ]; then
        error "Command <tt>$@</tt> FAILED"
    fi
}

get_persistent_device_from_unix_node() {
    local unix_device=$1
    local schema=$2
    local node
    local persistent_name
    node=$(basename "${unix_device}")
    for persistent_name in /dev/disk/"${schema}"/*; do
        if [ "$(basename "$(readlink "${persistent_name}")")" = "${node}" ];then
            if [[ ${persistent_name} =~ ^/dev/disk/"${schema}"/nvme-eui ]]; then
                # Filter out nvme-eui nodes as they are not descriptive to the user
                continue
            fi
            echo "${persistent_name}"
            return
        fi
    done
    warn "Could not find <tt>${schema}</tt> representation of <tt>${node}</tt>. Using original device <tt>${unix_device}</tt>"
    echo "${unix_device}"
}

probe_partitions() {
    local probe_dir=/var/lib/tik/probe
    local filesystem_type=$2
    local filematch=$3
    local device=$1
    local mountops
    local line
    local id
    local parttypename
    local fstype

    if [[ "${filesystem_type}" == "btrfs" ]]; then
        mountops="-o compress=zstd:1"
    fi
    prun /usr/bin/mkdir -p ${probe_dir}/mnt
    probedpart=""
    # List ID-LINK, PARTTYPENAME, and FSTYPE separated by a semicolon
    # (Note: we assume that neither the PARTTYPENAME nor the FSTYPE contain semicolons.)
    for line in $(lsblk "${device}" -p -n -r -o ID-LINK,PARTTYPENAME,FSTYPE | tr -s ' ' ";"); do
        # Split the line into three variables
        id=$(echo "$line" | cut -d";" -f1)
        parttypename=$(echo "$line" | cut -d";" -f2)
        fstype=$(echo "$line" | cut -d";" -f3)

        # Check if either PARTTYPENAME or FSTYPE matches the filesystem type
        if [ "$parttypename" = "$filesystem_type" ] || [ "$fstype" = "$filesystem_type" ]; then
            if [ -z "${filematch}" ]; then
                log "[probe_partitions] no file match required"
                probedpart="$(/usr/bin/readlink -f "/dev/disk/by-id/${id}")"
                log "[probe_partitions] Partition ${probedpart} found"
            else
                probed_part="$(/usr/bin/readlink -f "/dev/disk/by-id/${id}")"
                prun /usr/bin/mount ${mountops} ${probed_part} "${probe_dir}/mnt"
                if [ -f "${probe_dir}/mnt/${filematch}" ]; then
                    log "[probe_partitions] File ${filematch} found"
                    probedpart="${probed_part}"
                    log "[probe_partitions] Partition ${probedpart} found"
                    if grep -q 'PRETTY_NAME="openSUSE MicroOS"' "${probe_dir}/mnt/${filematch}" && [ -f "${probe_dir}/mnt/usr/bin/gnome-shell" ]; then
                        log "Legacy Aeon Install FOUND"
                        legacy_aeon=1
                    fi
                fi
                prun-opt /usr/bin/umount "${probe_dir}/mnt"
            fi
        fi
    done
    prun /usr/bin/rmdir "${probe_dir}/mnt"
}

bytes_to_human() {
    # Usage: bytes_to_human <bytes>
    numfmt --to=iec --suffix=B "$1"
}

free_bytes_on_disk() {
    local disk_in="$1"
    local disk
    local disk_size free_bytes

    # Resolve /dev/disk/by‑id/* -> /dev/sdX /dev/nvme…
    disk=$(readlink -f "${disk_in}")

    # Whole‑device size in bytes
    disk_size=$(prun /usr/sbin/blockdev --getsize64 "${disk}")

    # Sum the sizes whose 5‑th field begins with 'free'
    local parted_output
    parted_output=$(prun /usr/sbin/parted -m -s "${disk}" unit B print free)
    free_bytes=$(awk -F: '$5~/^free/ {gsub(/B/,"",$4); sum+=$4} END{print sum+0}' <<< "$parted_output")

    # log for debugging
    log "[free_bytes_on_disk] ${disk}: free space: ${free_bytes}"
    log "[free_bytes_on_disk] ${disk}: disk size: ${disk_size}"

    echo "${free_bytes:-0}"
}


get_disk() {
    # config: set TIK_SHOW_FREE_SPACE="true" to show free rows
    if [[ "${TIK_SHOW_FREE_SPACE:-}" == "true" ]]; then
        show_free=1
    fi

    # constants / locals
    tik_volid="TIKINSTALL"
    local disk_id="by-id"
    local disk_size disk_device disk_device_by_id
    local disk_meta disk_list
    local -a list_items
    local part_count part_size part_info part_fs
    # shellcheck disable=SC2054
    local blk_opts=(-p -n -r -o NAME,SIZE,TYPE)
    # shellcheck disable=SC2054
    local blk_opts_plus_label=(-p -n -r -o NAME,SIZE,TYPE,LABEL)
    # shellcheck disable=SC2054
    local blk_opts_part_info=(-p -n -r -o NAME,SIZE,TYPE,LABEL,FSTYPE)
    local usb_match_1="usb"  usb_match_2=":0"

    # identify the install media so we can ignore it
    tik_install_disk_part=$(lsblk "${blk_opts_plus_label[@]}" | tr -s ' ' ":" \
                            | grep ":${tik_volid}" | cut -f1 -d:)

    # iterate over every real disk
    for disk_meta in $(lsblk "${blk_opts[@]}" | grep -E "disk|raid" | tr ' ' ":"); do
        disk_size=$(echo "$disk_meta" | cut -f2 -d:)
        [[ "$disk_size" == "0B" ]] && continue # empty readers

        disk_device=$(echo "$disk_meta" | cut -f1 -d:)
        [[ $disk_device =~ ^/dev/fd   || $disk_device =~ ^/dev/zram ]] && continue
        [[ "$tik_install_disk_part" == "$disk_device"*   ]] && continue  # skip media

        # partition summary for the disk row
        part_count=0; part_info=""
        while IFS=: read -r _ part_size _ _ part_fs; do
            part_count=$((part_count + 1))
            part_info+="${part_info:+,}${part_fs:-unknown}(${part_size})"
        done < <(lsblk "${blk_opts_part_info[@]}" | grep -E "${disk_device}.+part.+" | tr ' ' ":")

        [[ $part_count -eq 0 ]] && part_info="none"

        # prefer /dev/disk/by-id/ symlink
        disk_device_by_id=$(get_persistent_device_from_unix_node \
                            "$disk_device" "$disk_id")

        # skip USB install targets unless explicitly allowed
        if [[ "${TIK_ALLOW_USB_INSTALL_DEVICES}" -ne 1 ]] && \
           { [[ "$disk_device_by_id" == *"$usb_match_1"* ]] || \
           [[ "$disk_device_by_id" == *"$usb_match_2"* ]]; }; then
            continue
        fi
        [[ -n $disk_device_by_id ]] && disk_device=$disk_device_by_id

        # normal whole‑disk entry
        list_items+=("$(basename "$disk_device")" "$disk_size" "$part_count" "$part_info")
        disk_list+=" $(basename "$disk_device") $disk_size"

        # optional free‑space entry
        if (( show_free )); then
            free_bytes=$(free_bytes_on_disk "$disk_device")
            disk_bytes=$(lsblk -b -n -d -o SIZE "$disk_device")
            diff_bytes=$(( disk_bytes - free_bytes ))

            if (( free_bytes >= 10*1024*1024*1024 && diff_bytes > 1024*1024*1024 )); then
                free_human=$(bytes_to_human "$free_bytes")
                list_items+=("free-$(basename "$disk_device")" "$free_human" "free" "free")
            fi
        fi
    done

    # CONFIG OVERRIDE: $TIK_INSTALL_DEVICE supplied by user / config file
    if [[ -n ${TIK_INSTALL_DEVICE:-} ]]; then
        local device=$TIK_INSTALL_DEVICE device_size device_meta
        [[ -e $device ]] || error "Given device <tt>$device</tt> does not exist."
        [[ -b $device ]] || error "Given device <tt>$device</tt> is not a block special."

        device_meta=$(lsblk "${blk_opts[@]}" "$device" | grep -E "disk|raid" | tr ' ' ":")
        device_size=$(echo "$device_meta" | cut -f2 -d:)

        list_items=("$(basename "$device")" "$device_size")
        disk_list="$(basename "$device") $device_size"
        if [ -z "$REPART_EMPTY" ]; then
            REPART_EMPTY="force"
        fi
        log "tik installation device set to: $device"
    else
        # sanity check
        [[ -z "${list_items[*]}" ]] && error "No device(s) for installation found."
        
        # build chooser / auto‑select logic
        local selectable_count=$(( $(echo "${list_items[*]}" | wc -w) / 4 ))
        
        if (( selectable_count > 1 )); then
            d --list --column=Disk --column=Size --column=Partitions --column=Filesystems \
              --width=1050 --height=340 \
              --title="Select A Disk${show_free:+ or Free Space}" \
              --text="Choose <b>either</b> a full disk${show_free:+ or a free‑space entry}.\n" \
              "${list_items[@]}"
        
            if (( show_free )) && [[ $result =~ ^free- ]]; then
                log "[get_disk] free space on disk selected"
                REPART_EMPTY="refuse"
                base_result=${result#free-}
                TIK_INSTALL_DEVICE="/dev/disk/$disk_id/$base_result"
                [[ ! -e $TIK_INSTALL_DEVICE ]] && TIK_INSTALL_DEVICE="/dev/$base_result"
            else
                log "[get_disk] full disk selected"
                REPART_EMPTY="force"
                TIK_INSTALL_DEVICE="/dev/disk/$disk_id/$result"
                [[ ! -e $TIK_INSTALL_DEVICE ]] && TIK_INSTALL_DEVICE="/dev/$result"
            fi
        else
            # only one candidate -> choose automatically
            log "[get_disk] only one disk available"
            read -r entry _ <<<"$disk_list"
            REPART_EMPTY="force"
            TIK_INSTALL_DEVICE="/dev/disk/$disk_id/$entry"
            [[ ! -e $TIK_INSTALL_DEVICE ]] && TIK_INSTALL_DEVICE="/dev/$entry"
        fi
    fi
}


get_img() {
    local list_items
    local message
    local img_meta
    local img_item
    local img_list
    local img_array
    local file_type
    # Images are assumed to be named to the following standard
    # $ProductName.$Version.raw.xz for block devices
    # $ProductName.$Version.raw for systemd-repart images
    # Any extraneous fields may confuse tik's detection, selection and presentation of the image to the user
    for file_type in '*.raw.xz' '*.raw';do
        for img_meta in $(cd $TIK_IMG_DIR && (stat --printf="%n\t%s\n" ${file_type} | tr '	' ":"));do
            img_filename="$(echo $img_meta | cut -f1 -d:)"
            img_size="$(echo $img_meta | cut -f2 -d:)"
            list_items="${list_items} ${img_filename} ${img_size}"
        done
    done
    if [ -n "${TIK_INSTALL_IMAGE}" ];then
        # install image overwritten by config.
        local img=${TIK_INSTALL_IMAGE}
        local img_meta
        local img_size
        if [ ! -e "${img}" ];then
            local no_img="Given image <tt>${img}</tt> does not exist."
            error "${no_img}"
        fi
        if [ ! -s "${img}" ];then
            local empty_img="Given image <tt>${img}</tt> is empty."
            error "${empty_img}"
        fi
        img_meta=$(
            eval cd $TIK_IMG_DIR && (stat --printf="%n\t%s\n" $img | tr '	' ":")
        )
        img_filename="$(echo $img_meta | cut -f1 -d:)"
        img_size="$(echo $img_meta | cut -f2 -d:)"
        list_items="${list_items} ${img_filename} ${img_size}"
        message="tik installation image set to to: ${img}"
        log "${message}"
    fi
    if [ -z "${list_items}" ];then
        TIK_INSTALL_IMAGE='TIK_SELFDEPLOY'
    fi
    img_list=${list_items}
    if [ -n "${img_list}" ];then
        local count=0
        local img_index=0
        for entry in ${img_list};do
            if [ $((count % 2)) -eq 0 ];then
                img_array[${img_index}]=${entry}
                img_index=$((img_index + 1))
            fi
            count=$((count + 1))
        done
        if [ "${img_index}" -eq 1 ];then
            # one single disk image found, use it
            TIK_INSTALL_IMAGE="${img_array[0]}"
        else
            # manually select from storage list
            d --list --column=Image --column=Size --title="Select A Image" --text="Select the operating system image to install.\n" ${list_items}
            TIK_INSTALL_IMAGE="$result"
        fi
    fi
}

reread_partitiontable() {
    # We've just done a lot to $TIK_INSTALL_DEVICE and it's probably a good idea to make sure the partition table is clearly read so tools like dracut dont get confused.
    log "[reread_partitiontable] Re-reading partition table"
    # sleeps added to let the system finish doing partition stuff before rereading, and then a few secs to finish reading the table.  Honestly, I'm not sure the 2nd one is needed, but doesn't hurt, so *shrug*
    sleep 3
    prun /usr/sbin/blockdev --rereadpt ${TIK_INSTALL_DEVICE}
    sleep 3
}

create_keyfile() {
    # Even if there's no partitions using encryption, systemd-repart will need a key-file defined for the --key-file parameter.
    tik_keyfile=$(prun mktemp /tmp/tik.XXXXXXXXXX)
    log "[create_keyfile] Creating keyfile ${tik_keyfile}"
    prun /usr/bin/dd bs=512 count=4 if=/dev/urandom of=${tik_keyfile} iflag=fullblock
    prun /usr/bin/chmod 400 ${tik_keyfile}
}

wipe_keyfile() {
    # We made a keyfile and need to clean it up at the end of the installation, possibly wiping it from the newly installed device
    log "[wipe_keyfile] Deleting keyfile ${tik_keyfile}"
    probe_partitions "${TIK_INSTALL_DEVICE}" "Linux\x20root\x20(x86-64)"
    if [ -n "${probedpart}" ]; then
        # Assumes Slot 0 is always by the key-file at enrolment
        prun /usr/bin/systemd-cryptenroll --unlock-key-file=${tik_keyfile} --wipe-slot=0 ${probedpart}
    fi
    # We're done with the key-file, so remove it
    prun /usr/bin/rm ${tik_keyfile}
}

dump_image() {
    local image_source_files=$1
    local image_target=$2

    local warn_text
    if [ "${REPART_EMPTY}" = "refuse" ]; then
        warn_text="Once the installation begins the changes to the selected disk are irreversible.\n\nThe system will be installed into the <b>existing free space</b> on the disk.\n\nThis setup receives <b>less official support</b> than a full‑disk installation and <b>might lead to an unbootable system or destroy other systems installed on this disk.</b>\n\nEnsure all data on this disk is backed up.\n\nContinue?"
    else
        warn_text="Once the installation begins the changes to the selected disk are irreversible.\n\n<b>Proceeding will fully erase the disk.</b>\n\nEnsure all data on this disk is backed up.\n\nContinue with installation?"
    fi

    d --question --no-wrap --title="Begin Installation?" --text="${warn_text}"

    case "${image_source_files}" in
        *.raw.xz)        dump_image_dd           "${image_source_files}" "${image_target}" ;;
        *.raw)           dump_image_repart_image "${image_source_files}" "${image_target}" ;;
        TIK_SELFDEPLOY)  dump_image_repart_self  "${image_target}" ;;
        *)               error "invalid image type provided" ;;
    esac
}

dump_image_dd() {
    local image_source_files=$1
    local image_target=$2
    log "[dump_image_dd] deploying ${TIK_IMG_DIR}/${image_source_files}"
    (xzcat ${TIK_IMG_DIR}/${image_source_files} | pv -f -F "# %b copied in %t %r" | prun /usr/bin/dd of=${image_target} bs=64k) 2>&1 | d --progress --title="Installing ${TIK_OS_NAME}" --pulsate --auto-close --no-cancel --width=400
    prun /usr/bin/sync | d --progress --title="Syncing" --pulsate --auto-close --no-cancel --width=400
}

dump_image_repart_image() {
    local image_source_files=$1
    local image_target=$2
    local success=0 max_attempts=5 attempt_num=1

    create_keyfile
    log "[dump_image_repart_image] deploying ${TIK_IMG_DIR}/${image_source_files}"

    while [ "${success}" = 0 ] && [ "${attempt_num}" -lt "${max_attempts}" ]; do
        prun-opt systemd-repart --no-pager --pretty=0 --dry-run=no \
                --key-file="${tik_keyfile}" \
                --image="${TIK_IMG_DIR}"/"${image_source_files}" \
                --image-policy=root=unprotected \
                --empty="${REPART_EMPTY}" \
                "${image_target}" \
                > >(d --progress --title="Installing ${TIK_OS_NAME}" \
                     --text="Deploying OS Image" --pulsate --auto-close --no-cancel \
                     --width=400)
        if [ "${retval}" -eq 0 ]; then
            success=1
        else
            log "[dump_image_repart_image] attempt $attempt_num failed – retry"
            sleep 1; attempt_num=$(( attempt_num + 1 ))
        fi
    done

    [ "${success}" = 1 ] || error "systemd-repart failed"
}

dump_image_repart_self() {
    local image_target=$1
    create_keyfile
    log "[dump_image_repart_self] self‑deploying"

    prun systemd-repart --no-pager --pretty=0 --dry-run=no \
        --key-file="${tik_keyfile}" \
        --empty="${REPART_EMPTY}" \
        "${image_target}" \
        > >(d --progress --title="Installing ${TIK_OS_NAME}" \
             --text="Deploying OS Image" --pulsate --auto-close --no-cancel \
             --width=400)
}

set_boot_target() {
    local efipartnum
    if [ "${debug}" == "1" ]; then
        log "[debug] Not setting EFI boot target"
    elif [ -n "${efi_already_set}" ]; then
        log "[set_boot_target] boot target already set, not setting again"
    else
        # Cleanup any existing openSUSE boot entries
        prun-opt /usr/sbin/efibootmgr -B -L "openSUSE Boot Manager"
        prun /usr/sbin/efibootmgr -O
        log "[set_boot_target] searching for ESP partition containing /EFI/systemd/shim.efi on ${TIK_INSTALL_DEVICE}"
        probe_partitions "${TIK_INSTALL_DEVICE}" "EFI\x20System" "/EFI/systemd/shim.efi"
        if [ -z "${probedpart}" ]; then
            error "esp partition not found"
        fi
        efipartnum=$(lsblk ${probedpart} -p -n -r -o PARTN)
        log "[set_boot_target] found ESP on ${probedpart}, partition number ${efipartnum}"
        prun /usr/sbin/efibootmgr -c -L "openSUSE Boot Manager" -d ${TIK_INSTALL_DEVICE} -l "\EFI\systemd\shim.efi" -p ${efipartnum}
        # Log to show the resulting eficonfig
        log "[set_boot_target] $(prun /usr/sbin/efibootmgr)"
        efi_already_set=1
    fi
}

load_modules() {
local module_dir
if [[ $2 = "custom" ]]; then
    module_dir=$TIK_CUSTOM_DIR/modules/$1
else
    module_dir=$tik_dir/modules/$1
fi
if [ -n "$(ls -A $module_dir)" ]; then
for f in $module_dir/*
    do
        tik_module="$f"
        log "[START] $module_dir/$f"
        . $f
        log "[STOP] $module_dir/$f"
    done
fi
tik_module="tik"
}