import socket
import random
import os


def get_used_ports():
    """
    從 /proc/net/tcp 和 /proc/net/udp 讀取系統已占用的 port
    直接讀取 Linux 系統資訊，避免暴力嘗試
    
    Returns:
        set: 已被占用的 port 號集合
    """
    used_ports = set()
    
    # 讀取 TCP 占用的 port (包含 IPv4 和 IPv6)
    for filepath in ['/proc/net/tcp', '/proc/net/tcp6']:
        if os.path.exists(filepath):
            try:
                with open(filepath, 'r') as f:
                    lines = f.readlines()[1:]  # 跳過標題行
                    for line in lines:
                        parts = line.split()
                        if len(parts) > 1:
                            # local_address 格式: "0100007F:1F90" (hex)
                            local_addr = parts[1]
                            port_hex = local_addr.split(':')[1]
                            port = int(port_hex, 16)
                            used_ports.add(port)
            except (IOError, PermissionError):
                pass
    
    # 讀取 UDP 占用的 port (包含 IPv4 和 IPv6)
    for filepath in ['/proc/net/udp', '/proc/net/udp6']:
        if os.path.exists(filepath):
            try:
                with open(filepath, 'r') as f:
                    lines = f.readlines()[1:]  # 跳過標題行
                    for line in lines:
                        parts = line.split()
                        if len(parts) > 1:
                            local_addr = parts[1]
                            port_hex = local_addr.split(':')[1]
                            port = int(port_hex, 16)
                            used_ports.add(port)
            except (IOError, PermissionError):
                pass
    
    return used_ports


def is_port_available(port):
    """
    測試指定 port 是否可用 (TCP 和 UDP 都測試)
    
    Args:
        port (int): 要測試的 port 號
    
    Returns:
        bool: True 表示可用，False 表示被占用
    """
    # 測試 TCP
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind(('', port))
    except OSError:
        return False
    
    # 測試 UDP
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind(('', port))
    except OSError:
        return False
    
    return True


def find_available_port(start_port=1024, end_port=65535):
    """
    在指定的 port 區間內隨機找出一個未被占用的 port
    使用 Linux /proc/net 系統資訊來過濾已占用的 port，避免暴力嘗試
    確保 TCP 和 UDP 都可用
    
    Args:
        start_port (int): 起始 port 號 (預設 1024)
        end_port (int): 結束 port 號 (預設 65535)
    
    Returns:
        int: 可用的 port 號，如果找不到則返回 None
    """
    if start_port < 1 or end_port > 65535 or start_port >= end_port:
        raise ValueError("Port 範圍必須在 1-65535 之間，且起始 port 必須小於結束 port")
    
    # 從系統讀取已占用的 port
    used_ports = get_used_ports()
    
    # 計算可用的 port 列表 (排除已占用的)
    available_ports = [p for p in range(start_port, end_port + 1) if p not in used_ports]
    
    if not available_ports:
        return None
    
    # 隨機打亂順序
    random.shuffle(available_ports)
    
    # 從可用列表中挑選，再用 socket 雙重確認 TCP 和 UDP 都可用
    for port in available_ports:
        if is_port_available(port):
            return port
    
    # 如果都不可用
    return None


if __name__ == "__main__":
    # 使用範例
    port = find_available_port(8000, 9000)
    if port:
        print(f"找到可用的 port: {port}")
    else:
        print("找不到可用的 port")
    
    # 自訂範圍範例
    port = find_available_port(10000, 20000)
    if port:
        print(f"在 10000-20000 範圍找到可用的 port: {port}")
