#!/bin/bash
set -Eeuo pipefail

PROGRAM_NAME="$(basename "$0")"
DEFAULT_CONFIG="/etc/zhct-anhui/network.env"
STATE_DIR="/var/lib/zhct-anhui-network"
MYSQL_DEFAULTS_FILE="/etc/zhct-anhui/mysql.cnf"
CONFIG_FILE="$DEFAULT_CONFIG"
ACTION=""

usage() {
    cat <<EOF
用法：
  $PROGRAM_NAME --check [--config 配置文件]
  $PROGRAM_NAME --apply [--config 配置文件]
  $PROGRAM_NAME --rollback [--state 状态文件]

说明：
  --check     只检查配置、网卡、IP 冲突和服务，不修改网络
  --apply     保存当前网络状态后切换 IP
  --rollback  恢复最近一次 --apply 前的网络配置
EOF
}

log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*"; }
die() { printf '错误：%s\n' "$*" >&2; exit 1; }

require_root() {
    [ "$(id -u)" -eq 0 ] || die "请先执行 sudo -i，再运行本脚本"
}

valid_ipv4() {
    local ip="$1" part
    [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
    IFS='.' read -r -a parts <<< "$ip"
    for part in "${parts[@]}"; do
        [ "$part" -ge 0 ] 2>/dev/null && [ "$part" -le 255 ] || return 1
    done
}

load_config() {
    [ -f "$CONFIG_FILE" ] || die "配置文件不存在：$CONFIG_FILE"
    # 配置文件由 root 管理，内容仅允许网络参数赋值。
    # shellcheck disable=SC1090
    source "$CONFIG_FILE"
    : "${SITE_IP:?缺少 SITE_IP}"
    : "${PREFIX:?缺少 PREFIX}"
    : "${GATEWAY:?缺少 GATEWAY}"
    : "${DNS1:?缺少 DNS1}"
    : "${DNS2:?缺少 DNS2}"
    : "${CONNECTION_NAME:?缺少 CONNECTION_NAME}"
    : "${DEVICE_NAME:?缺少 DEVICE_NAME}"
}

validate_config() {
    valid_ipv4 "$SITE_IP" || die "SITE_IP 格式错误：$SITE_IP"
    valid_ipv4 "$GATEWAY" || die "GATEWAY 格式错误：$GATEWAY"
    valid_ipv4 "$DNS1" || die "DNS1 格式错误：$DNS1"
    valid_ipv4 "$DNS2" || die "DNS2 格式错误：$DNS2"
    [[ "$PREFIX" =~ ^[0-9]+$ ]] && [ "$PREFIX" -ge 1 ] && [ "$PREFIX" -le 32 ] || die "PREFIX 必须为 1-32"
    nmcli -t -f NAME connection show | grep -Fxq "$CONNECTION_NAME" || die "NetworkManager 连接不存在：$CONNECTION_NAME"
    ip link show "$DEVICE_NAME" >/dev/null 2>&1 || die "网卡不存在：$DEVICE_NAME"

    if command -v python3 >/dev/null 2>&1; then
        python3 - "$SITE_IP" "$PREFIX" "$GATEWAY" <<'PY'
import ipaddress
import sys
network = ipaddress.ip_network(f"{sys.argv[1]}/{sys.argv[2]}", strict=False)
gateway = ipaddress.ip_address(sys.argv[3])
if gateway not in network:
    raise SystemExit(f"错误：网关 {gateway} 不在目标网段 {network} 内")
PY
    fi
}

check_ip_conflict() {
    local current_ips
    current_ips="$(ip -o -4 addr show dev "$DEVICE_NAME" | awk '{print $4}' | cut -d/ -f1)"
    if grep -Fxq "$SITE_IP" <<< "$current_ips"; then
        log "目标 IP 已配置在本机，跳过冲突探测"
        return 0
    fi
    if command -v arping >/dev/null 2>&1; then
        log "探测目标 IP 是否冲突：$SITE_IP"
        arping -D -c 2 -w 3 -I "$DEVICE_NAME" "$SITE_IP" >/dev/null 2>&1 || die "目标 IP 可能已被占用：$SITE_IP"
    else
        log "警告：未安装 arping，无法自动检查 IP 冲突"
    fi
}

show_plan() {
    printf '%s\n' \
        "连接名：$CONNECTION_NAME" \
        "网卡：$DEVICE_NAME" \
        "目标 IP：$SITE_IP/$PREFIX" \
        "网关：$GATEWAY" \
        "DNS：$DNS1,$DNS2"
}

service_check() {
    log "关键监听端口"
    ss -lntp | awk 'NR == 1 || $4 ~ /:(22|80|81|3306|6379)$/'
    log "Nginx 配置检查"
    /usr/local/nginx/sbin/nginx -t
}

database_check() {
    command -v mysql >/dev/null 2>&1 || die "未安装 mysql 客户端"
    [ -f "$MYSQL_DEFAULTS_FILE" ] || die "MySQL 客户端配置不存在：$MYSQL_DEFAULTS_FILE"
    [ "$(stat -c '%u:%a' "$MYSQL_DEFAULTS_FILE")" = "0:600" ] || die "MySQL 客户端配置必须为 root:root 600"
    local row_count
    row_count="$(mysql --defaults-extra-file="$MYSQL_DEFAULTS_FILE" --batch --skip-column-names \
        -e "SELECT COUNT(*) FROM zhct_anhui.ydy_config WHERE config_key='ossHost';")"
    [ "$row_count" = "1" ] || die "ydy_config 中 ossHost 记录数应为 1，实际为：$row_count"
    log "数据库连接和 ossHost 唯一性检查通过"
}

update_oss_host() {
    local expected="http://$SITE_IP" actual
    log "更新 zhct_anhui.ydy_config：ossHost=$expected"
    mysql --defaults-extra-file="$MYSQL_DEFAULTS_FILE" \
        -e "UPDATE zhct_anhui.ydy_config SET config_value='$expected' WHERE config_key='ossHost';"
    actual="$(mysql --defaults-extra-file="$MYSQL_DEFAULTS_FILE" --batch --skip-column-names \
        -e "SELECT config_value FROM zhct_anhui.ydy_config WHERE config_key='ossHost' LIMIT 1;")"
    [ "$actual" = "$expected" ] || die "ossHost 回读验证失败，实际值：$actual"
    log "ossHost 更新并回读验证成功"
}

precheck() {
    require_root
    command -v nmcli >/dev/null 2>&1 || die "未安装 nmcli"
    systemctl is-active --quiet NetworkManager || die "NetworkManager 未运行"
    load_config
    validate_config
    show_plan
    check_ip_conflict
    database_check
    service_check
    log "检查通过；--check 模式未修改任何配置"
}

save_state() {
    local state_file="$1" method addresses gateway dns
    method="$(nmcli -g ipv4.method connection show "$CONNECTION_NAME")"
    addresses="$(nmcli -g ipv4.addresses connection show "$CONNECTION_NAME")"
    gateway="$(nmcli -g ipv4.gateway connection show "$CONNECTION_NAME")"
    dns="$(nmcli -g ipv4.dns connection show "$CONNECTION_NAME")"
    umask 077
    {
        printf 'CONNECTION_NAME=%q\n' "$CONNECTION_NAME"
        printf 'DEVICE_NAME=%q\n' "$DEVICE_NAME"
        printf 'OLD_METHOD=%q\n' "$method"
        printf 'OLD_ADDRESSES=%q\n' "$addresses"
        printf 'OLD_GATEWAY=%q\n' "$gateway"
        printf 'OLD_DNS=%q\n' "$dns"
    } > "$state_file"
    ln -sfn "$state_file" "$STATE_DIR/latest"
}

apply_change() {
    precheck
    mkdir -p "$STATE_DIR"
    chmod 700 "$STATE_DIR"
    local state_file="$STATE_DIR/state-$(date +%Y%m%d-%H%M%S).env"
    save_state "$state_file"
    log "旧网络配置已保存：$state_file"
    log "开始写入新网络配置；当前 SSH 连接可能立即断开"
    nmcli connection modify "$CONNECTION_NAME" \
        ipv4.method manual \
        ipv4.addresses "$SITE_IP/$PREFIX" \
        ipv4.gateway "$GATEWAY" \
        ipv4.dns "$DNS1,$DNS2"
    nmcli connection up "$CONNECTION_NAME"
    ip -4 address show dev "$DEVICE_NAME"
    ip -4 route
    ip -o -4 addr show dev "$DEVICE_NAME" | awk '{print $4}' | grep -Fxq "$SITE_IP/$PREFIX" || die "新 IP 未生效"
    ip -4 route show default | grep -Fq "via $GATEWAY" || die "默认网关未生效"
    /usr/local/nginx/sbin/nginx -t
    systemctl reload nginx
    update_oss_host
    service_check
    log "IP 切换完成，新 SSH 地址：ssh user@$SITE_IP"
}

rollback_change() {
    require_root
    local state_file="${ROLLBACK_STATE:-$STATE_DIR/latest}"
    [ -e "$state_file" ] || die "没有找到可回退的状态文件：$state_file"
    # 状态文件由本脚本以 root:root 0600 创建。
    # shellcheck disable=SC1090
    source "$state_file"
    nmcli -t -f NAME connection show | grep -Fxq "$CONNECTION_NAME" || die "原连接不存在：$CONNECTION_NAME"
    log "恢复连接：$CONNECTION_NAME，原模式：$OLD_METHOD"
    if [ "$OLD_METHOD" = "auto" ]; then
        nmcli connection modify "$CONNECTION_NAME" \
            ipv4.method auto ipv4.addresses "" ipv4.gateway "" ipv4.dns ""
    else
        [ -n "$OLD_ADDRESSES" ] || die "原静态 IP 为空，无法自动回退"
        nmcli connection modify "$CONNECTION_NAME" \
            ipv4.method "$OLD_METHOD" \
            ipv4.addresses "$OLD_ADDRESSES" \
            ipv4.gateway "$OLD_GATEWAY" \
            ipv4.dns "$OLD_DNS"
    fi
    nmcli connection up "$CONNECTION_NAME"
    ip -4 address show dev "$DEVICE_NAME"
    ip -4 route
    log "网络配置已回退"
}

while [ "$#" -gt 0 ]; do
    case "$1" in
        --check|--apply|--rollback)
            [ -z "$ACTION" ] || die "只能指定一个操作"
            ACTION="$1"
            shift
            ;;
        --config)
            [ "$#" -ge 2 ] || die "--config 缺少参数"
            CONFIG_FILE="$2"
            shift 2
            ;;
        --state)
            [ "$#" -ge 2 ] || die "--state 缺少参数"
            ROLLBACK_STATE="$2"
            shift 2
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *) die "未知参数：$1" ;;
    esac
done

case "$ACTION" in
    --check) precheck ;;
    --apply) apply_change ;;
    --rollback) rollback_change ;;
    *) usage; exit 1 ;;
esac
