#!/bin/bash
#
# 局域网Ping扫描脚本 - 跨平台兼容 (macOS + Linux)
# 支持: macOS, Debian/Ubuntu, RHEL/CentOS/Fedora, Alpine, Arch Linux 等
# 用法: ./lan-ping-scan.sh [选项]
#
# 示例:
#   ./lan-ping-scan.sh                          # 自动检测网段, 扫描1-254
#   ./lan-ping-scan.sh 192.168.1                # 扫描指定网段1-254
#   ./lan-ping-scan.sh -r 10-50 192.168.1       # 扫描192.168.1.10-50
#   ./lan-ping-scan.sh -r 100-200 10.0.0 2 100  # 自定义范围和参数
#

set -euo pipefail

# ========== 颜色输出 ==========
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'

log_info()  { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok()    { echo -e "${GREEN}[OK]${NC}   $1"; }
log_warn()  { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERR]${NC}  $1"; }

# ========== 参数解析 ==========
SUBNET=""
TIMEOUT=1
MAX_PARALLEL=50
IP_RANGE_START=1
IP_RANGE_END=254

# 手动解析参数，支持灵活顺序
while [[ $# -gt 0 ]]; do
    case "$1" in
        -r|--range)
            if [[ -z "${2:-}" ]]; then
                log_error "-r 参数需要指定范围，如: -r 10-50"
                exit 1
            fi
            IP_RANGE_START=$(echo "$2" | cut -d'-' -f1)
            IP_RANGE_END=$(echo "$2" | cut -d'-' -f2)
            # 验证范围是数字
            if ! [[ "$IP_RANGE_START" =~ ^[0-9]+$ ]] || ! [[ "$IP_RANGE_END" =~ ^[0-9]+$ ]]; then
                log_error "无效的范围格式: $2 (应为: 起始-结束，如 10-50)"
                exit 1
            fi
            shift 2
            ;;
        -h|--help)
            show_help
            ;;
        -*)
            log_error "未知选项: $1"
            echo "使用 -h 查看帮助"
            exit 1
            ;;
        *)
            # 位置参数：网段、超时、并发数
            if [[ -z "$SUBNET" ]]; then
                SUBNET="$1"
            elif [[ "$TIMEOUT" == "1" ]] && [[ "$1" =~ ^[0-9]+$ ]]; then
                TIMEOUT="$1"
            elif [[ "$MAX_PARALLEL" == "50" ]] && [[ "$1" =~ ^[0-9]+$ ]]; then
                MAX_PARALLEL="$1"
            else
                log_error "无法识别的参数: $1"
                exit 1
            fi
            shift
            ;;
    esac
done

# ========== 函数: 获取本机IP ==========
get_local_ip() {
    local ip=""

    # macOS 优先用 networksetup
    if [[ "$(uname)" == "Darwin" ]]; then
        # 方法1: route + ifconfig (macOS)
        local iface=$(route get default 2>/dev/null | grep 'interface:' | awk '{print $2}')
        if [ -n "$iface" ]; then
            ip=$(ifconfig "$iface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | head -1)
        fi
    fi

    # Linux 方法
    if [ -z "$ip" ] && command -v ip &>/dev/null; then
        ip=$(ip route get 1.1.1.1 2>/dev/null | grep -o 'src [^ ]*' | awk '{print $2}' | head -1)
    fi

    if [ -z "$ip" ] && command -v ifconfig &>/dev/null; then
        ip=$(ifconfig 2>/dev/null | grep -E 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -1)
    fi

    if [ -z "$ip" ] && command -v hostname &>/dev/null; then
        ip=$(hostname -I 2>/dev/null | awk '{print $1}')
    fi

    echo "$ip"
}

# ========== 函数: 从IP提取网段 ==========
extract_subnet() {
    local full_ip="$1"
    echo "$full_ip" | awk -F. '{print $1"."$2"."$3}'
}

# ========== 函数: 验证IP格式 ==========
validate_subnet() {
    local subnet="$1"
    # 使用兼容的正则表达式 (不用 -P)
    if echo "$subnet" | grep -qE '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'; then
        return 0
    else
        return 1
    fi
}

# ========== 函数: Ping单个IP ==========
ping_host() {
    local target_ip="$1"
    local timeout="$2"
    local ping_timeout_flag=""

    # macOS 用 -t, Linux 用 -W
    if [[ "$(uname)" == "Darwin" ]]; then
        ping_timeout_flag="-t $timeout"
    else
        ping_timeout_flag="-W $timeout"
    fi

    if ping -c 1 $ping_timeout_flag "$target_ip" &>/dev/null; then
        # 获取主机名 (兼容macOS和Linux)
        local hostname=""
        if command -v host &>/dev/null; then
            hostname=$(host "$target_ip" 2>/dev/null | grep 'domain name pointer' | awk '{print $NF}' || true)
        elif command -v nslookup &>/dev/null; then
            hostname=$(nslookup "$target_ip" 2>/dev/null | grep -i 'name =' | awk '{print $NF}' || true)
        fi

        if [ -n "$hostname" ]; then
            echo "ALIVE  $target_ip  ($hostname)"
        else
            echo "ALIVE  $target_ip"
        fi
        return 0
    fi
    return 1
}

# ========== 函数: 显示帮助 ==========
show_help() {
    cat << EOF
${CYAN}局域网Ping扫描脚本${NC} - 跨平台兼容 (macOS + Linux)

${BLUE}用法:${NC}
  $0 [选项] [网段] [超时时间] [并发数]

${BLUE}选项:${NC}
  -r START-END    指定IP扫描范围 (默认: 1-254)
  -h              显示此帮助信息

${BLUE}参数:${NC}
  网段            目标网段前三段 (默认: 自动检测本机网段)
  超时时间        ping超时秒数 (默认: 1)
  并发数          最大并行ping数 (默认: 50)

${BLUE}示例:${NC}
  $0                           # 自动检测, 扫描1-254
  $0 192.168.1                 # 扫描192.168.1.1-254
  $0 -r 10-50 192.168.1        # 扫描192.168.1.10-50
  $0 -r 100-200 10.0.0 2 100   # 扫描10.0.0.100-200, 超时2秒, 100并发

EOF
    exit 0
}

# ========== 主流程 ==========

# 显示帮助
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
    show_help
fi

# 获取网段
if [ -z "$SUBNET" ]; then
    log_info "未指定网段,正在检测本机IP..."
    LOCAL_IP=$(get_local_ip)

    if [ -z "$LOCAL_IP" ]; then
        log_error "无法获取本机IP,请手动指定网段"
        echo "用法: $0 <网段前三段>"
        echo "示例: $0 192.168.1"
        exit 1
    fi

    SUBNET=$(extract_subnet "$LOCAL_IP")
    log_ok "检测到本机IP: $LOCAL_IP"
    log_info "扫描网段: $SUBNET.0/24"
else
    # 验证用户输入的网段
    if ! validate_subnet "$SUBNET"; then
        log_error "无效的网段格式: $SUBNET"
        echo "请使用格式: X.X.X (例如: 192.168.1)"
        exit 1
    fi
    log_info "扫描网段: $SUBNET.0/24"
fi

# 验证IP范围
if [ "${IP_RANGE_START:-1}" -lt 1 ] || [ "${IP_RANGE_END:-254}" -gt 254 ] || [ "${IP_RANGE_START:-1}" -gt "${IP_RANGE_END:-254}" ]; then
    log_error "无效的IP范围: ${IP_RANGE_START:-1}-${IP_RANGE_END:-254} (必须在1-254之间, 且起始<=结束)"
    exit 1
fi

# 检查ping命令
if ! command -v ping &>/dev/null; then
    log_error "未找到ping命令"
    exit 1
fi

TOTAL_IPS=$(( ${IP_RANGE_END:-254} - ${IP_RANGE_START:-1} + 1 ))
log_info "扫描范围: ${SUBNET}.${IP_RANGE_START:-1} - ${SUBNET}.${IP_RANGE_END:-254} (共${TOTAL_IPS}个IP)"
log_info "超时设置: ${TIMEOUT}秒 | 并发数: $MAX_PARALLEL"
log_info "开始扫描..."
echo "-------------------------------------------"

# 记录开始时间
START_TIME=$(date +%s)

# 创建临时文件
ALIVE_FILE=$(mktemp /tmp/lan-scan-alive-XXXXXX)
ALL_IPS_FILE=$(mktemp /tmp/lan-scan-all-XXXXXX)
trap "rm -f $ALIVE_FILE $ALL_IPS_FILE" EXIT

# 生成所有待扫描IP列表
for i in $(seq "${IP_RANGE_START:-1}" "${IP_RANGE_END:-254}"); do
    echo "$SUBNET.$i" >> "$ALL_IPS_FILE"
done

# 并发ping扫描
ACTIVE_PIDS=0
while IFS= read -r TARGET; do
    ping_host "$TARGET" "$TIMEOUT" >> "$ALIVE_FILE" &

    ACTIVE_PIDS=$((ACTIVE_PIDS + 1))

    # 控制并发数
    if [ "$ACTIVE_PIDS" -ge "$MAX_PARALLEL" ]; then
        wait -n 2>/dev/null || true
        ACTIVE_PIDS=$((ACTIVE_PIDS - 1))
    fi
done < "$ALL_IPS_FILE"

# 等待所有后台任务完成
wait

# 计算耗时
END_TIME=$(date +%s)
ELAPSED=$((END_TIME - START_TIME))

# 统计结果
ALIVE_COUNT=$(grep -c "^ALIVE" "$ALIVE_FILE" 2>/dev/null || echo "0")
DEAD_COUNT=$(( ${TOTAL_IPS:-0} - ${ALIVE_COUNT:-0} ))

# 提取活跃IP列表
ALIVE_IPS_FILE=$(mktemp /tmp/lan-scan-ips-XXXXXX)
grep "^ALIVE" "$ALIVE_FILE" | awk '{print $2}' | sort -t. -k4 -n > "$ALIVE_IPS_FILE" 2>/dev/null || true

echo "-------------------------------------------"
echo ""
echo -e "${GREEN}═══════════════════════════════════════════${NC}"
echo -e "${GREEN}  扫描完成! 耗时: ${ELAPSED}秒${NC}"
echo -e "${GREEN}═══════════════════════════════════════════${NC}"
echo ""

# 活跃主机
if [ "$ALIVE_COUNT" -gt 0 ]; then
    echo -e "${GREEN}[活跃主机] ${ALIVE_COUNT} 台${NC}"
    echo "-------------------------------------------"
    sort -t. -k4 -n "$ALIVE_FILE"
    echo ""
fi

# 未使用IP
if [ "$DEAD_COUNT" -gt 0 ]; then
    echo -e "${YELLOW}[未使用IP] ${DEAD_COUNT} 个${NC}"
    echo "-------------------------------------------"

    # 生成未使用IP列表
    while IFS= read -r ip; do
        if ! grep -q "^${ip}$" "$ALIVE_IPS_FILE" 2>/dev/null; then
            echo "UNUSED $ip"
        fi
    done < "$ALL_IPS_FILE" | sort -t. -k4 -n

    echo ""
fi

# 汇总
echo -e "${CYAN}[汇总]${NC}"
echo "  总扫描IP数: $TOTAL_IPS"
echo -e "  活跃主机:   ${GREEN}${ALIVE_COUNT}${NC}"
echo -e "  未使用IP:   ${YELLOW}${DEAD_COUNT}${NC}"
echo ""
log_info "扫描范围: ${SUBNET}.${IP_RANGE_START:-1} - ${SUBNET}.${IP_RANGE_END:-254}"

# 清理
rm -f "$ALIVE_IPS_FILE"

exit 0
