#!/bin/sh
#
# Copyright (C) 2025  Etersoft
# Copyright (C) 2025  Vitaly Lipatov <lav@etersoft.ru>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

# Load ALT Linux mirror database from config file
# Format: name URL (space-separated)
# Sets __ALT_MIRROR_DB (all mirrors) and __ALT_MIRROR_DB_VISIBLE (without legacy)
__load_alt_mirror_db()
{
    [ -n "$__ALT_MIRROR_DB" ] && return
    local mirror_file="$CONFIGDIR/mirrors-alt.list"
    if [ -f "$mirror_file" ] ; then
        __ALT_MIRROR_DB="$(grep -v '^#' "$mirror_file" | grep -v '^$')"
        # visible mirrors: everything before "# Legacy" line
        __ALT_MIRROR_DB_VISIBLE="$(sed -n '/^# Legacy/q;p' "$mirror_file" | grep -v '^#' | grep -v '^$')"
    else
        fatal "Mirror list not found: $mirror_file"
    fi
}

# Convert URL to change pattern for epm repo change
# https://mirror.yandex.ru/altlinux -> //mirror.yandex.ru altlinux
__url_to_change_pattern()
{
    echo "$1" | sed -e 's|^https:||' -e 's|/\([^/]*\)$| \1|'
}

# Test file path for speed test (relative to full_url)
__MIRROR_TEST_PATH="Sisyphus/x86_64/base/pkglist.classic.xz"

# Get change pattern for mirror by name (for epm repo change)
__alt_mirror_change_pattern()
{
    __load_alt_mirror_db
    local mirror_name="$1"
    echo "$__ALT_MIRROR_DB" | while read -r name url ; do
        [ "$name" = "$mirror_name" ] && __url_to_change_pattern "$url" && break
    done
}

# Get full URL for mirror by name
__alt_mirror_url()
{
    __load_alt_mirror_db
    local mirror_name="$1"
    echo "$__ALT_MIRROR_DB" | while read -r name url ; do
        [ "$name" = "$mirror_name" ] && echo "$url" && break
    done
}

# Extract domain from URL (e.g., https://mirror.yandex.ru/altlinux -> mirror.yandex.ru)
__url_to_domain()
{
    echo "$1" | sed -e 's|^[a-z]*://||' -e 's|/.*||'
}

# Get current mirror from repo list by matching domain against known mirrors
__get_current_mirror()
{
    __load_alt_mirror_db
    local repo_url
    repo_url="$(epm --quiet repo list 2>/dev/null | head -1)"
    [ -z "$repo_url" ] && return

    local name url domain
    while read -r name url ; do
        [ -z "$name" ] && continue
        domain=$(__url_to_domain "$url")
        case "$repo_url" in
            *"$domain"*)
                echo "$name"
                return
                ;;
        esac
    done <<EOF
$__ALT_MIRROR_DB
EOF
}

# List mirrors in human-readable or TSV format
# Args: current_mirror [short]
# Uses __ALT_MIRROR_DB_VISIBLE (excludes legacy mirrors)
__list_mirrors()
{
    __load_alt_mirror_db
    local current_mirror="$1"
    local short="$2"
    local name url
    while read -r name url ; do
        [ -z "$name" ] && continue
        local is_current=0
        [ "$name" = "$current_mirror" ] && is_current=1
        if [ -n "$short" ] ; then
            # short mode: just names
            if [ -n "$tsv" ] ; then
                printf "%s\t%s\n" "$name" "$is_current"
            elif [ "$is_current" = 1 ] ; then
                set_boldcolor $GREEN
                printf "%s " "$name"
                restore_color
            else
                printf "%s " "$name"
            fi
        elif [ -n "$tsv" ] ; then
            printf "%s\t%s\t%s\n" "$name" "$url" "$is_current"
        else
            # full mode: name, marker, url
            if [ "$is_current" = 1 ] ; then
                set_boldcolor $GREEN
                printf "%-15s *  %s\n" "$name" "$url"
                restore_color
            else
                printf "%-15s    %s\n" "$name" "$url"
            fi
        fi
    done <<EOF
$__ALT_MIRROR_DB_VISIBLE
EOF
    [ -n "$short" ] && [ -z "$tsv" ] && echo ""
    return 0
}

# Measure download speed for a URL using eget --speedtest
# Returns speed in MB/s or 0 on error
__measure_speed()
{
    local url="$1"
    local speed
    # eget does 3 measurements with 5 sec timeout, filters outliers, outputs MB/s
    speed=$(eget --timeout 5 --speedtest --tsv "$url" 2>/dev/null | cut -f2)
    [ -n "$speed" ] && echo "$speed" || echo "0"
}

# Format speed in human-readable form (speed is already in MB/s from eget)
__format_speed()
{
    local speed="$1"
    if [ "$speed" = "0" ] || [ -z "$speed" ] ; then
        echo "error"
    else
        echo "${speed} MB/s"
    fi
}

# Test single mirror (called as background job)
# Args: tmpdir name mirror_url
__test_single_mirror()
{
    local tmpdir="$1"
    local name="$2"
    local mirror_url="$3"
    local test_url="${mirror_url}/${__MIRROR_TEST_PATH}"
    local speed
    speed=$(__measure_speed "$test_url")
    echo "${speed}|${name}|${mirror_url}" > "$tmpdir/$name"
}

# Test all mirrors and collect results
# Outputs results in real-time to stderr, returns sorted results to stdout
# Uses __ALT_MIRROR_DB_VISIBLE (excludes legacy mirrors)
__test_all_mirrors()
{
    __load_alt_mirror_db
    local current_mirror="$1"
    local results=""

    # Test each mirror sequentially with live output
    local name url
    while read -r name url ; do
        [ -z "$name" ] && continue
        local test_url="${url}/${__MIRROR_TEST_PATH}"

        # Show testing status (skip in tsv mode)
        [ -z "$tsv" ] && printf "%-15s testing...  %s" "$name" "$url" >&2

        local speed
        speed=$(__measure_speed "$test_url")
        local formatted
        formatted=$(__format_speed "$speed")

        # Clear line and show result (skip in tsv mode)
        [ -z "$tsv" ] && printf "\r%-15s %12s     %s\n" "$name" "$formatted" "$url" >&2

        results="${results}${speed}|${name}|${url}
"
    done <<EOF
$__ALT_MIRROR_DB_VISIBLE
EOF

    # Sort by speed (descending) and format output
    echo "$results" | sort -t'|' -k1 -rg | while IFS='|' read -r speed name url ; do
        [ -z "$name" ] && continue
        local formatted
        formatted=$(__format_speed "$speed")
        local status=""
        [ "$name" = "$current_mirror" ] && status="*"
        if [ -n "$tsv" ] ; then
            printf "%s\t%s\t%s\t%s\n" "$name" "$speed" "$url" "$status"
        else
            printf "%-15s %12s  %-1s  %s\n" "$name" "$formatted" "$status" "$url"
        fi
    done
}

# Interactive mirror selection with fzf
__select_mirror_fzf()
{
    local results="$1"
    local selected

    if is_command fzf && [ -c /dev/tty ] ; then
        selected=$(echo "$results" | \
            fzf --prompt="$(eval_gettext "Select mirror (Enter=apply, Esc=cancel)"): " \
                --header="$(eval_gettext "Current mirror marked with *")" \
                --height=12 \
                --reverse \
                --no-multi)
        if [ -n "$selected" ] ; then
            # Extract mirror name (first field)
            echo "$selected" | awk '{print $1}'
        fi
    else
        # Fallback: numbered menu
        echo "$results" | nl -w2 -s') ' >&2
        echo "" >&2
        printf "%s" "$(eval_gettext "Enter number to switch mirror, or press Enter to cancel"): " >&2
        local choice
        read_tty choice || return 1
        [ -z "$choice" ] && return 1
        local line
        line=$(echo "$results" | sed -n "${choice}p")
        [ -n "$line" ] && echo "$line" | awk '{print $1}'
    fi
}

epm_repomirrors()
{
    local CMD="$1"
    [ -n "$CMD" ] && shift
    case $CMD in
    -h|--help|help)                       # HELPCMD: show this help
        echo "Usage: epm repo mirrors [command]"
        echo "List and test ALT Linux mirrors"
        echo ""
        echo "Commands:"
        get_help HELPCMD "$SHAREDIR/epm-repomirrors"
        return
        ;;
    ""|list)                              # HELPCMD: list available mirrors with URLs (use --short for names only)
        __epm_repomirrors_check_alt
        local current_mirror
        current_mirror=$(__get_current_mirror)
        __list_mirrors "$current_mirror" "$short"
        ;;
    speedtest)                            # HELPCMD: test download speed and show interactive selection
        __epm_repomirrors_check_alt
        __epm_repomirrors_speedtest
        ;;
    auto)                                 # HELPCMD: test speed and automatically switch to fastest mirror
        __epm_repomirrors_check_alt
        __epm_repomirrors_speedtest --auto
        ;;
    *)
        fatal "Unknown command $ epm repo mirrors $CMD"
        ;;
    esac
}

__epm_repomirrors_check_alt()
{
    case $BASEDISTRNAME in
        "alt")
            ;;
        *)
            fatal "epm repo mirrors is only supported for ALT Linux"
            ;;
    esac
}

__epm_repomirrors_speedtest()
{
    local opt_auto=""
    [ "$1" = "--auto" ] && opt_auto=1

    local current_mirror
    current_mirror=$(__get_current_mirror)

    # Speed test mode (uses eget --speedtest)
    [ -z "$tsv" ] && info "Testing mirrors speed (3 measurements × 5 sec per mirror)..."

    [ -z "$tsv" ] && echo "" >&2
    local results
    results=$(__test_all_mirrors "$current_mirror")
    [ -z "$tsv" ] && echo "" >&2

    # --auto mode: switch to fastest
    if [ -n "$opt_auto" ] ; then
        local fastest
        fastest=$(echo "$results" | head -1 | awk '{print $1}')
        if [ -n "$fastest" ] && [ "$fastest" != "$current_mirror" ] ; then
            info "Switching to fastest mirror: $fastest"
            docmd epm repo change "$fastest"
        else
            info "Already using the fastest mirror: $current_mirror"
        fi
        return
    fi

    # TSV mode: just output results
    if [ -n "$tsv" ] ; then
        echo "$results"
        return
    fi

    # Interactive mode: select with fzf
    if [ -z "$non_interactive" ] && is_command fzf && [ -c /dev/tty ] ; then
        local selected
        selected=$(__select_mirror_fzf "$results")
        if [ -n "$selected" ] ; then
            if [ "$selected" = "$current_mirror" ] ; then
                info "Already using mirror: $selected"
            else
                info "Switching to mirror: $selected"
                docmd epm repo change "$selected"
            fi
        fi
    else
        # No fzf or non-interactive: show sorted results
        echo "$results"
    fi
}
