#!/bin/bash
# Description: 更新镜像源
# ==============================================================================
# Arch Linux Mirror Updater (Optimized & Standardized)
#
# Description:
#   Automates the Arch Linux mirrorlist update process with input validation,
#   smart regional fallback, and a global safety net.
#   Backups are stored in ~/.cache/mirror-update/.
#
# 功能说明:
#   自动化更新 Arch Linux 镜像源。具备输入有效性校验、智能区域扩展重试、
#   以及全球极速兜底机制。备份文件保存在 ~/.cache/mirror-update/。
# ==============================================================================

# Bash Best Practices:
# -e: Exit immediately if a command exits with a non-zero status (handled carefully with if statements).
# -u: Treat unset variables as an error when substituting.
# -o pipefail: The return value of a pipeline is the status of the last command to exit with a non-zero status.
set -uo pipefail

# --- Pre-Configuration ---
# Detect language safely
ORIGINAL_LANG="${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}"
if [[ "$ORIGINAL_LANG" == *"zh_CN"* ]]; then
    readonly UI_LANG="zh"
else
    readonly UI_LANG="en"
fi

# Force C locale for internal command consistency (dates, grep, sorting)
export LC_ALL=C

# --- Configuration ---
readonly MIRRORLIST="/etc/pacman.d/mirrorlist"
readonly BACKUP_DIR="$HOME/.cache/mirror-update"
readonly BACKUP_PATH="$BACKUP_DIR/mirrorlist.bak"

# Base Args: Protocol https, sort by score, verbose output
# Using --fastest is applied dynamically in the functions
readonly BASE_ARGS=("--protocol" "https" "--sort" "rate" "--verbose")

# --- Styles ---
readonly H_RED='\033[1;31m'
readonly H_GREEN='\033[1;32m'
readonly H_YELLOW='\033[1;33m'
readonly H_BLUE='\033[1;34m'
readonly H_CYAN='\033[1;36m'
readonly NC='\033[0m'

# --- Localization ---
# Note: Strings are cleaner now, without parenthetical implementation details.
LANG_TABLE='
MSG_DESC          | Smart mirror updater with validation & fallback.                | 具备校验与回退功能的智能镜像更新器。
MSG_USAGE_TITLE   | Usage:                                          | 用法:
MSG_OPT_TITLE     | Options:                                        | 选项:
MSG_EX_TITLE      | Examples:                                       | 示例:
MSG_USAGE_H       | Show this help message                          | 显示帮助信息
MSG_USAGE_C       | Manually specify country (skips detection)      | 手动指定国家/地区代码 (跳过检测)
MSG_EX_AUTO       | # Auto detect                                   | # 自动检测
MSG_EX_FORCE      | # Force Japan                                   | # 强制指定日本
MSG_REQ_SUDO      | Requesting sudo privileges...                   | 请求管理员权限...
MSG_DETECTING     | Detecting location via timezone...              | 正在通过时区检测所在地区...
MSG_TRY_API       | Timezone unknown. Attempting IP geolocation...  | 时区无法确认，尝试通过 IP 定位...
MSG_DETECTED      | Detected Region:                                | 检测到地区:
MSG_CONFIRM       | Use this region? [Y/n]:                         | 确认使用该地区吗? [Y/n]:
MSG_MANUAL_SET    | Manual Mode selected. Region:                   | 已选择手动模式。目标地区:
MSG_BACKUP_DONE   | Backup saved to:                                | 备份已保存至:
MSG_TOP_MIRRORS   | Top 3 Mirrors:                                  | 评分最高的前 3 个镜像:
MSG_INPUT_CODE    | Please enter country code (e.g. cn, jp, us):    | 请输入国家代码 (例如 cn, jp, us):
MSG_INPUT_MANUAL  | Enter country manually:                         | 请手动输入国家名称或代码:
MSG_ERR_EMPTY     | No country specified.                           | 未指定国家。
MSG_CANCEL        | Cancelled.                                      | 操作已取消。
MSG_RESTORED      | Restored backup.                                | 已恢复备份。
MSG_FAIL_RETRY    | Update failed. Retrying with broader region...  | 更新失败。正在扩展到周边区域重试...
MSG_FAIL_GLOBAL   | Region failed. Switching to Global Mode...      | 区域更新失败。切换到“全球极速”模式...
MSG_FAIL_FINAL    | All attempts failed.                            | 所有尝试均失败。
MSG_ATTEMPT_1     | [Attempt 1/3] Target:                           | [尝试 1/3] 目标:
MSG_ATTEMPT_2     | [Attempt 2/3] Region:                           | [尝试 2/3] 区域:
MSG_ATTEMPT_3     | [Attempt 3/3] Global Mode                       | [尝试 3/3] 全球模式
MSG_VALIDATING    | Validating country:                             | 正在校验国家有效性:
MSG_INVALID_CTRY  | Invalid country name/code:                      | 无效的国家名称/代码:
MSG_VALID_LIST    | Hint: Use reflector --list-countries to see list. | 提示: 使用 reflector --list-countries 查看有效列表。
MSG_SWITCH_GLOBAL | Invalid country. Switch to Global Mode? [Y/n]   | 无效的国家。切换到“全球极速”模式吗？[Y/n]
MSG_WARN_GLOBAL   | Could not detect loc. Defaulting to Global Mode.| 无法检测位置。默认为“全球极速”模式。
MSG_WARN_NO_SET   | No country set. Starting with Global Mode...    | 未设置国家。正在启动“全球极速”模式...
MSG_FINAL_BACKUP  | Backup file location:                           | 备份文件位置:
MSG_ERR_FILE_EMPTY| Error: Resulting mirrorlist is empty!           | 错误: 生成的镜像列表为空！
MSG_CMD_PREVIEW   | Running:                                        | 正在执行:
'

# Localization Parser
# Using a loop to dynamically assign variables based on UI_LANG
while IFS='|' read -r v e c; do
    v=$(printf '%s' "$v" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
    e=$(printf '%s' "$e" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
    c=$(printf '%s' "$c" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
    [[ -z "$v" ]] && continue
    case "$v" in \#*) continue ;; esac
    if [[ "$UI_LANG" == "zh" ]]; then eval "$v=\"$c\""; else eval "$v=\"$e\""; fi
done <<EOF
$LANG_TABLE
EOF

# --- Helper Functions ---
# Using printf is safer and more portable than echo -e
log()     { printf "${H_BLUE}[INFO]${NC} %s\n" "$1"; }
success() { printf "${H_GREEN}[OK]${NC} %s\n" "$1"; }
warn()    { printf "${H_YELLOW}[WARN]${NC} %s\n" "$1"; }
error()   { printf "${H_RED}[ERROR]${NC} %s\n" "$1"; exit 1; }

usage() {
    cat <<EOF
$MSG_USAGE_TITLE $(basename "$0") [options]

$MSG_DESC

$MSG_OPT_TITLE
  -c, --country <name>   $MSG_USAGE_C
  -h, --help             $MSG_USAGE_H

$MSG_EX_TITLE
  $(basename "$0")                  $MSG_EX_AUTO
  $(basename "$0") -c Japan         $MSG_EX_FORCE
  $(basename "$0") -c "United States"
EOF
}

detect_country_by_timezone() {
    local TZ
    if command -v timedatectl >/dev/null 2>&1; then
        TZ=$(timedatectl show -p Timezone --value)
    else
        TZ=$(cat /etc/timezone 2>/dev/null || echo "")
    fi
    case "$TZ" in
        *"Shanghai"*|*"Chongqing"*|*"Urumqi"*|*"Harbin"*) echo "China" ;;
        *"Hong_Kong"*) echo "Hong Kong" ;;
        *"Taipei"*)    echo "Taiwan" ;;
        *"Tokyo"*|*"Osaka"*) echo "Japan" ;;
        *"Singapore"*) echo "Singapore" ;;
        *"Seoul"*)     echo "South Korea" ;;
        *"New_York"*|*"Los_Angeles"*|*"Chicago"*|*"Denver"*) echo "United States" ;;
        *"London"*|*"Belfast"*) echo "United Kingdom" ;;
        *"Berlin"*)    echo "Germany" ;;
        *"Paris"*)     echo "France" ;;
        *"Toronto"*|*"Vancouver"*) echo "Canada" ;;
        *) echo "Unknown" ;;
    esac
}

detect_country_by_api() { curl -s --max-time 3 https://ipapi.co/country_name; }

get_fallback_region() {
    local c="$1"
    case "$c" in
        *"China"*|*"Hong Kong"*|*"Taiwan"*) echo "China,Hong Kong,Taiwan,Japan,Singapore" ;;
        *"Japan"*|*"Korea"*)               echo "Japan,South Korea,Taiwan,Hong Kong" ;;
        *"Singapore"*|*"Malaysia"*)        echo "Singapore,Malaysia,Thailand,Indonesia,China" ;;
        *"United States"*|*"Canada"*)      echo "United States,Canada" ;;
        *"Germany"*|*"France"*|*"United Kingdom"*) echo "Germany,France,United Kingdom,Netherlands" ;;
        *) echo "" ;; 
    esac
}

validate_country() {
    local input="$1"
    # Caching valid countries in a global variable if not set
    if [[ -z "${VALID_COUNTRIES:-}" ]]; then
        VALID_COUNTRIES=$(reflector --list-countries | tr '[:upper:]' '[:lower:]')
    fi
    local lower_input
    lower_input=$(echo "$input" | tr '[:upper:]' '[:lower:]')
    
    if echo "$VALID_COUNTRIES" | grep -qF "$lower_input"; then
        return 0
    else
        # Allow 2-letter ISO codes implicitly (reflector supports them)
        if [[ ${#lower_input} -eq 2 ]]; then return 0; fi
        return 1
    fi
}

run_reflector_with_retry() {
    local target="$1"
    local choice
    
    printf "${H_BLUE}[CHECK]${NC} %s '%s'... " "$MSG_VALIDATING" "$target"
    if validate_country "$target"; then
        printf "${H_GREEN}OK${NC}\n"
    else
        printf "${H_RED}FAIL${NC}\n"
        warn "$MSG_INVALID_CTRY $target"
        printf "%s\n" "$MSG_VALID_LIST"
        printf "${H_YELLOW}%s ${NC}" "$MSG_SWITCH_GLOBAL"
        read -r choice
        choice=${choice:-Y}
        if [[ "$choice" =~ ^[yY] ]]; then return 1; else error "$MSG_CANCEL"; fi
    fi

    local fallback
    fallback=$(get_fallback_region "$target")
    
    # === Attempt 1: Target Country ===
    # Using --fastest 10 instead of -f or --number
    printf "${H_CYAN}%s ${H_GREEN}%s${NC}\n" "$MSG_ATTEMPT_1" "$target"
    printf "${H_BLUE}%s${NC} sudo reflector %s --age 12 --fastest 10 --country %s\n" "$MSG_CMD_PREVIEW" "${BASE_ARGS[*]}" "$target"
    
    # Using 'tee' to show output and write file. 'pipefail' ensures we catch reflector errors.
    if sudo reflector "${BASE_ARGS[@]}" --age 12 --fastest 10 --country "$target" | sudo tee "$MIRRORLIST"; then
        return 0
    fi

    # === Attempt 2: Regional Fallback ===
    if [[ -n "$fallback" ]]; then
        warn "$MSG_FAIL_RETRY"
        printf "${H_CYAN}%s ${H_GREEN}%s${NC}\n" "$MSG_ATTEMPT_2" "$fallback"
        printf "${H_BLUE}%s${NC} sudo reflector %s --age 24 --fastest 10 --download-timeout 5 --country %s\n" "$MSG_CMD_PREVIEW" "${BASE_ARGS[*]}" "$fallback"
        
        if sudo reflector "${BASE_ARGS[@]}" --age 24 --fastest 10 --download-timeout 5 --country "$fallback" | sudo tee "$MIRRORLIST"; then
            return 0
        fi
    fi

    # === Attempt 3: Global (Returns 1 to trigger main loop fallback) ===
    return 1
}

# --- Main Execution ---

# Variable initialization
MANUAL_COUNTRY=""

while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help) usage; exit 0 ;;
        -c|--country)
            if [[ -n "${2:-}" ]]; then MANUAL_COUNTRY="$2"; shift 2
            else error "Missing argument for --country"; fi ;;
        *) printf "Unknown option: %s\n" "$1"; usage; exit 1 ;;
    esac
done

printf "\n${H_CYAN}=== Arch Linux Mirror Updater ===${NC}\n"

# 1. Determine Target
if [[ -n "$MANUAL_COUNTRY" ]]; then
    TARGET_COUNTRY="$MANUAL_COUNTRY"
    log "$MSG_MANUAL_SET ${H_GREEN}$TARGET_COUNTRY${NC}"
else
    log "$MSG_DETECTING"
    DETECTED=$(detect_country_by_timezone)
    if [[ "$DETECTED" == "Unknown" ]]; then
        warn "$MSG_TRY_API"
        API_RESULT=$(detect_country_by_api)
        if [[ -n "$API_RESULT" ]] && [[ ! "$API_RESULT" =~ "error" ]]; then DETECTED="$API_RESULT"; fi
    fi
    
    if [[ "$DETECTED" == "Unknown" ]] || [[ -z "$DETECTED" ]]; then
        warn "$MSG_WARN_GLOBAL"
        TARGET_COUNTRY=""
    else
        printf "${H_YELLOW}%s ${H_CYAN}%s${NC}. %s " "$MSG_DETECTED" "$DETECTED" "$MSG_CONFIRM"
        read -r CONFIRM
        CONFIRM=${CONFIRM:-Y}
        if [[ "$CONFIRM" =~ ^[yY] ]]; then TARGET_COUNTRY="$DETECTED"
        else
            printf "${H_YELLOW}%s ${NC}" "$MSG_INPUT_MANUAL"
            read -r TARGET_COUNTRY
        fi
    fi
fi

# 2. Prepare System & Backup
log "$MSG_REQ_SUDO"
if ! sudo -v; then error "Root privileges required."; fi

mkdir -p "$BACKUP_DIR"
if cp "$MIRRORLIST" "$BACKUP_PATH" 2>/dev/null; then
    log "$MSG_BACKUP_DONE $BACKUP_PATH"
else
    warn "Failed to create backup in user directory. Trying sudo..."
    sudo cp "$MIRRORLIST" "$BACKUP_PATH"
    log "$MSG_BACKUP_DONE $BACKUP_PATH (Root owned)"
fi

# 3. Execute with Retry Logic
SUCCESS=false

if [[ -n "$TARGET_COUNTRY" ]]; then
    if run_reflector_with_retry "$TARGET_COUNTRY"; then
        SUCCESS=true
    else
        SUCCESS=false
    fi
fi

# 4. Global Fallback
if [[ "$SUCCESS" == "false" ]]; then
    if [[ -z "$TARGET_COUNTRY" ]]; then warn "$MSG_WARN_NO_SET"; else warn "$MSG_FAIL_GLOBAL"; fi
    
    printf "${H_CYAN}%s${NC}\n" "$MSG_ATTEMPT_3"
    printf "${H_BLUE}%s${NC} sudo reflector %s --latest 50 --fastest 10 --download-timeout 5\n" "$MSG_CMD_PREVIEW" "${BASE_ARGS[*]}"
    
    if sudo reflector "${BASE_ARGS[@]}" --latest 50 --fastest 10 --download-timeout 5 | sudo tee "$MIRRORLIST"; then
        SUCCESS=true
    fi
fi

# 5. Result Handling & Integrity Check
if [[ "$SUCCESS" == "true" ]]; then
    # CRITICAL CHECK: Verify file is not empty
    if [[ ! -s "$MIRRORLIST" ]]; then
        printf "\n"
        error "$MSG_ERR_FILE_EMPTY"
    else
        printf "\n"
        printf "${H_BLUE}%s${NC} %s\n" "$MSG_FINAL_BACKUP" "$BACKUP_PATH"
        exit 0
    fi
fi

# Failure Fallback
printf "\n"
error "$MSG_FAIL_FINAL"
if sudo cp "$BACKUP_PATH" "$MIRRORLIST"; then warn "$MSG_RESTORED"; else error "Failed to restore backup!"; fi
