#!/usr/bin/env bash
#
# 默认出站源地址管理器 Pro
# 功能：智能管理服务器多 IP 出站策略
# 作者：顶尖定制版
# 版本：v2.0
#

set -euo pipefail

# ==================== 配置 ====================

STATE_DIR="/var/lib/default-src-manager"
STATE_FILE="$STATE_DIR/config.json"
BACKUP_DIR="$STATE_DIR/backups"
LOG_FILE="$STATE_DIR/manager.log"

SERVICE_NAME="default-src-manager.service"
SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME"
APPLY_SCRIPT="/usr/local/sbin/default-src-apply"

# ==================== 颜色 ====================

if [[ -t 1 ]]; then
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    BLUE='\033[0;34m'
    CYAN='\033[0;36m'
    BOLD='\033[1m'
    DIM='\033[2m'
    RESET='\033[0m'
else
    RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' DIM='' RESET=''
fi

# ==================== 工具函数 ====================

log() {
    local level="$1"
    shift
    local msg="$*"
    local timestamp
    timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
    echo "[$timestamp] [$level] $msg" >> "$LOG_FILE"
}

info() {
    echo -e "${CYAN}[INFO]${RESET} $*"
    log "INFO" "$*"
}

success() {
    echo -e "${GREEN}[OK]${RESET} $*"
    log "OK" "$*"
}

warn() {
    echo -e "${YELLOW}[WARN]${RESET} $*"
    log "WARN" "$*"
}

error() {
    echo -e "${RED}[ERROR]${RESET} $*" >&2
    log "ERROR" "$*"
}

die() {
    error "$*"
    exit 1
}

section() {
    echo
    echo -e "${BOLD}${BLUE}━━━━ $* ━━━━${RESET}"
    echo
}

kv() {
    printf "  ${DIM}%-18s${RESET} %s\n" "$1:" "$2"
}

check_root() {
    [[ $EUID -eq 0 ]] || die "需要 root 权限运行"
}

check_deps() {
    local missing=()
    for cmd in ip jq curl ping systemctl; do
        command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd")
    done
    
    if [[ ${#missing[@]} -gt 0 ]]; then
        die "缺少依赖: ${missing[*]}"
    fi
}

init_dirs() {
    mkdir -p "$STATE_DIR" "$BACKUP_DIR"
    touch "$LOG_FILE"
}

# ==================== IP 工具 ====================

is_private_ipv4() {
    local ip="$1"
    [[ "$ip" =~ ^10\. ]] && return 0
    [[ "$ip" =~ ^192\.168\. ]] && return 0
    [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]] && return 0
    return 1
}

get_ip_score() {
    local ip="$1"
    # 优先级：172.16-31 > 10 > 192.168
    if [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]]; then
        echo 300
    elif [[ "$ip" =~ ^10\. ]]; then
        echo 200
    elif [[ "$ip" =~ ^192\.168\. ]]; then
        echo 100
    else
        echo 0
    fi
}

# ==================== 环境检测 ====================

detect_environment() {
    info "正在检测网络环境..."
    
    local iface gw public_ips=() private_ips=()
    
    # 检测默认网卡和网关
    iface="$(ip -4 route show default | awk 'NR==1{print $5}')"
    gw="$(ip -4 route show default | awk 'NR==1{print $3}')"
    
    [[ -n "$iface" ]] || die "未找到默认网卡"
    [[ -n "$gw" ]] || die "未找到默认网关"
    
    # 收集所有 IP
    while IFS= read -r line; do
        local ip cidr
        cidr="$(echo "$line" | awk '{print $4}')"
        ip="${cidr%/*}"
        
        if is_private_ipv4 "$ip"; then
            private_ips+=("$ip")
        else
            public_ips+=("$ip")
        fi
    done < <(ip -o -4 addr show dev "$iface" scope global 2>/dev/null)
    
    [[ ${#public_ips[@]} -gt 0 ]] || die "未找到公网 IP"
    [[ ${#private_ips[@]} -gt 0 ]] || die "未找到内网 IP"
    
    # 选择最优内网 IP（优先 172.16-31）
    local best_private="${private_ips[0]}"
    local best_score=0
    
    for ip in "${private_ips[@]}"; do
        local score
        score="$(get_ip_score "$ip")"
        if [[ $score -gt $best_score ]]; then
            best_score=$score
            best_private="$ip"
        fi
    done
    
    # 保存配置
    jq -n \
        --arg iface "$iface" \
        --arg gw "$gw" \
        --arg public "${public_ips[0]}" \
        --arg private "$best_private" \
        '{
            interface: $iface,
            gateway: $gw,
            public_ip: $public,
            private_ip: $private,
            detected_at: (now | strftime("%Y-%m-%d %H:%M:%S"))
        }' > "$STATE_FILE"
    
    success "环境检测完成"
    kv "网卡" "$iface"
    kv "网关" "$gw"
    kv "公网 IP" "${public_ips[0]}"
    kv "内网 IP" "$best_private"
    
    if [[ ${#private_ips[@]} -gt 1 ]]; then
        info "检测到多个内网 IP，已自动选择优先级最高的"
    fi
}

load_config() {
    [[ -f "$STATE_FILE" ]] || die "配置文件不存在，请先运行检测"
    
    IFACE="$(jq -r '.interface' "$STATE_FILE")"
    GATEWAY="$(jq -r '.gateway' "$STATE_FILE")"
    PUBLIC_IP="$(jq -r '.public_ip' "$STATE_FILE")"
    PRIVATE_IP="$(jq -r '.private_ip' "$STATE_FILE")"
}

# ==================== 策略应用 ====================

backup_routes() {
    local backup_file="$BACKUP_DIR/routes-$(date +%Y%m%d-%H%M%S).txt"
    {
        echo "=== Default Route ==="
        ip route show default
        echo
        echo "=== Policy Rules ==="
        ip rule show
        echo
        echo "=== Table 100 ==="
        ip route show table 100 2>/dev/null || echo "empty"
        echo
        echo "=== Table 200 ==="
        ip route show table 200 2>/dev/null || echo "empty"
    } > "$backup_file"
    
    info "已备份当前路由配置到 $backup_file"
}

clean_policy() {
    info "清理现有策略..."
    ip rule del pref 100 2>/dev/null || true
    ip rule del pref 110 2>/dev/null || true
    ip route flush table 100 2>/dev/null || true
    ip route flush table 200 2>/dev/null || true
    ip route flush cache 2>/dev/null || true
}

apply_private_default() {
    load_config
    backup_routes
    
    section "应用内网 IP 默认出站"
    
    info "配置主路由表..."
    ip route replace default via "$GATEWAY" dev "$IFACE" src "$PRIVATE_IP"
    
    info "配置策略路由表..."
    clean_policy
    
    # 表 100: 内网 IP 源
    ip route replace default via "$GATEWAY" dev "$IFACE" src "$PRIVATE_IP" table 100
    ip rule add pref 100 from "$PRIVATE_IP/32" table 100
    
    # 表 200: 公网 IP 源
    ip route replace default via "$GATEWAY" dev "$IFACE" src "$PUBLIC_IP" table 200
    ip rule add pref 110 from "$PUBLIC_IP/32" table 200
    
    ip route flush cache 2>/dev/null || true
    
    success "策略应用完成"
    verify_routes
}

rollback_public_default() {
    load_config
    backup_routes
    
    section "恢复公网 IP 默认出站"
    
    clean_policy
    ip route replace default via "$GATEWAY" dev "$IFACE" src "$PUBLIC_IP"
    ip route flush cache 2>/dev/null || true
    
    success "已恢复为公网 IP 默认出站"
    verify_routes
}

# ==================== 验证测试 ====================

verify_routes() {
    echo
    info "当前路由状态："
    echo
    echo "  主默认路由："
    ip route show default | sed 's/^/    /'
    echo
    echo "  策略规则："
    ip rule show | grep -E "100|110" | sed 's/^/    /' || echo "    (无)"
}

test_connectivity() {
    load_config
    
    section "连通性测试"
    
    # 测试 1: 默认路由选路
    info "测试默认新连接选路..."
    ip route get 1.1.1.1 | sed 's/^/  /'
    echo
    
    # 测试 2: 内网 IP 出站
    info "测试内网 IP 出站..."
    if ping -I "$PRIVATE_IP" -c 2 -W 3 1.1.1.1 >/dev/null 2>&1; then
        success "内网 IP 出站正常"
    else
        warn "内网 IP 出站失败"
    fi
    
    # 测试 3: 公网 IP 查询
    info "查询实际出口 IP..."
    local exit_ip
    exit_ip="$(curl -4 --interface "$PRIVATE_IP" --connect-timeout 5 --max-time 10 -s https://api.ipify.org 2>/dev/null || echo "查询失败")"
    kv "出口 IP" "$exit_ip"
    
    # 测试 4: DNS 解析
    info "测试 DNS 解析..."
    if host -W 3 google.com >/dev/null 2>&1; then
        success "DNS 解析正常"
    else
        warn "DNS 解析异常"
    fi
}

# ==================== 开机自启 ====================

install_service() {
    load_config
    
    section "安装开机自启"
    
    # 创建应用脚本
    cat > "$APPLY_SCRIPT" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

STATE_FILE="/var/lib/default-src-manager/config.json"
[[ -f "$STATE_FILE" ]] || exit 1

IFACE="$(jq -r '.interface' "$STATE_FILE")"
GATEWAY="$(jq -r '.gateway' "$STATE_FILE")"
PUBLIC_IP="$(jq -r '.public_ip' "$STATE_FILE")"
PRIVATE_IP="$(jq -r '.private_ip' "$STATE_FILE")"

# 清理
ip rule del pref 100 2>/dev/null || true
ip rule del pref 110 2>/dev/null || true
ip route flush table 100 2>/dev/null || true
ip route flush table 200 2>/dev/null || true

# 应用
ip route replace default via "$GATEWAY" dev "$IFACE" src "$PRIVATE_IP"
ip route replace default via "$GATEWAY" dev "$IFACE" src "$PRIVATE_IP" table 100
ip route replace default via "$GATEWAY" dev "$IFACE" src "$PUBLIC_IP" table 200
ip rule add pref 100 from "$PRIVATE_IP/32" table 100
ip rule add pref 110 from "$PUBLIC_IP/32" table 200
ip route flush cache 2>/dev/null || true
EOF
    
    chmod +x "$APPLY_SCRIPT"
    
    # 创建 systemd 服务
    cat > "$SERVICE_FILE" <<EOF
[Unit]
Description=Default Source IP Manager
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=$APPLY_SCRIPT
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF
    
    systemctl daemon-reload
    systemctl enable --now "$SERVICE_NAME"
    
    success "开机自启已安装"
    systemctl status "$SERVICE_NAME" --no-pager || true
}

remove_service() {
    section "移除开机自启"
    
    systemctl disable --now "$SERVICE_NAME" 2>/dev/null || true
    rm -f "$SERVICE_FILE" "$APPLY_SCRIPT"
    systemctl daemon-reload
    
    success "开机自启已移除"
}

# ==================== 状态显示 ====================

show_status() {
    if [[ ! -f "$STATE_FILE" ]]; then
        warn "尚未检测环境，请先运行选项 1"
        return
    fi
    
    load_config
    
    section "当前状态"
    
    kv "网卡" "$IFACE"
    kv "网关" "$GATEWAY"
    kv "公网 IP" "$PUBLIC_IP"
    kv "内网 IP" "$PRIVATE_IP"
    
    local detected_at
    detected_at="$(jq -r '.detected_at' "$STATE_FILE")"
    kv "检测时间" "$detected_at"
    
    echo
    info "当前默认路由："
    ip route show default | sed 's/^/  /'
    
    if ip route show default | grep -q "src $PRIVATE_IP"; then
        echo
        success "当前模式：内网 IP 默认出站"
    elif ip route show default | grep -q "src $PUBLIC_IP"; then
        echo
        info "当前模式：公网 IP 默认出站"
    else
        echo
        warn "当前模式：未识别"
    fi
}

# ==================== 菜单 ====================

show_menu() {
    clear
    echo -e "${BOLD}${CYAN}"
    echo "╔════════════════════════════════════════════╗"
    echo "║   默认出站源地址管理器 Pro v2.0           ║"
    echo "╚════════════════════════════════════════════╝"
    echo -e "${RESET}"
    
    show_status 2>/dev/null || echo
    
    echo
    echo -e "${BOLD}功能菜单${RESET}"
    echo
    echo "  ${GREEN}1${RESET}) 检测网络环境"
    echo "  ${GREEN}2${RESET}) 应用内网 IP 默认出站"
    echo "  ${GREEN}3${RESET}) 恢复公网 IP 默认出站"
    echo "  ${GREEN}4${RESET}) 测试连通性"
    echo "  ${GREEN}5${RESET}) 查看详细状态"
    echo "  ${GREEN}6${RESET}) 安装开机自启"
    echo "  ${GREEN}7${RESET}) 移除开机自启"
    echo "  ${GREEN}8${RESET}) 清理策略规则"
    echo "  ${GREEN}9${RESET}) 查看操作日志"
    echo "  ${RED}0${RESET}) 退出"
    echo
}

# ==================== 主函数 ====================

main() {
    check_root
    check_deps
    init_dirs
    
    while true; do
        show_menu
        read -rp "请选择 [0-9]: " choice
        echo
        
        case "$choice" in
            1)
                detect_environment
                read -rp "按回车继续..."
                ;;
            2)
                apply_private_default
                read -rp "按回车继续..."
                ;;
            3)
                rollback_public_default
                read -rp "按回车继续..."
                ;;
            4)
                test_connectivity
                read -rp "按回车继续..."
                ;;
            5)
                section "详细路由状态"
                echo "主路由表："
                ip route show | sed 's/^/  /'
                echo
                echo "策略规则："
                ip rule show | sed 's/^/  /'
                echo
                echo "表 100（内网 IP 源）："
                ip route show table 100 2>/dev/null | sed 's/^/  /' || echo "  (空)"
                echo
                echo "表 200（公网 IP 源）："
                ip route show table 200 2>/dev/null | sed 's/^/  /' || echo "  (空)"
                read -rp "按回车继续..."
                ;;
            6)
                install_service
                read -rp "按回车继续..."
                ;;
            7)
                remove_service
                read -rp "按回车继续..."
                ;;
            8)
                clean_policy
                success "策略规则已清理"
                read -rp "按回车继续..."
                ;;
            9)
                section "最近 20 条日志"
                tail -20 "$LOG_FILE" 2>/dev/null || echo "日志为空"
                read -rp "按回车继续..."
                ;;
            0)
                echo "再见！"
                exit 0
                ;;
            *)
                warn "无效选项"
                sleep 1
                ;;
        esac
    done
}

main "$@"
