import socket
import time
from threading import Thread, Lock
from typing import List, Optional
from loguru import logger

from qlsdk.core.message import UDPMessage
from qlsdk.core.local import get_ips

'''
    广播器类，用于发送和接收设备广播消息
    主要功能：发送设备搜索消息，接收设备连接消息
    注意：广播端口需要和ar4sdk做区分，使用54366时不能和x8同时使用
'''
class UdpBroadcaster:
    # 广播端口需要和ar4sdk做区分， 使用54366时不能和x8同时使用
    # 设备端监听的广播端口，固定：54366
    def __init__(self, ipv4_address: Optional[str] = None, port=54366):
        
        # self.server_ip = ipv4_address  # 用户指定的服务器IP
        self.devices_to_broadcast = []  # 待广播的设备序列号列表
        self.connected_devices = set()  # 已连接的设备序列号集合
        self.lock = Lock()  # 用于线程安全的锁
        self.running = True

        # 创建UDP socket
        # self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        self.ipv4_address = ipv4_address
        self.broadcast_port = port
        self.sockets: List[socket.socket] = []
        self.running = False
        self.lock = Lock()
        self.current_index = 0
        
        # 创建 sockets
        if self.ipv4_address is not None:
            self._create_socket_for_address(self.ipv4_address)
        else:
            self._create_sockets_for_all_addresses()
        
        # 启动广播线程
        self.broadcast_thread = Thread(target=self._broadcast_loop, daemon=True)
        self.running = True
        self.broadcast_thread.start()

    # 添加设备序列号到待广播列表
    def add_device(self, device_no:str):
        
        with self.lock:
            if device_no not in self.devices_to_broadcast:
                self.devices_to_broadcast.append(device_no)
                logger.info(f"添加设备[{device_no}]到搜索列表。")

    # 从待广播列表移除设备序列号
    def remove_device(self, device_no:str):
        
        with self.lock:
            if device_no in self.devices_to_broadcast:
                self.devices_to_broadcast.remove(device_no)
                logger.info(f"把设备[{device_no}]从搜索列表中移除。")

    # 把设备标记为已连接
    def mark_device_as_connected(self, device_id):
        with self.lock:
            # 如果设备已连接，则从搜索列表中移除
            if device_id in self.devices_to_broadcast:
                self.devices_to_broadcast.remove(device_id)
                
            # 添加到已连接设备集合
            self.connected_devices.add(device_id)
            
            logger.info(f"设备[{device_id}]已连接，从搜索列表中移除。")
            
    def _create_socket_for_address(self, address: str):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
            sock.bind((address, 0))
            self.sockets.append(sock)
        except Exception as e:
            logger.warning(f"在[{address}]上创建socket失败: {e}")
    
    def _create_sockets_for_all_addresses(self):
        addresses =  get_ips()
        for addr in addresses:
            self._create_socket_for_address(addr)
    
    def _broadcast_loop(self):
        while self.running:
            with self.lock:
                if not self.devices_to_broadcast:
                    time.sleep(2)
                    continue
                
                # 轮询序列号
                serial_no = self.devices_to_broadcast[self.current_index]
                self.current_index = (self.current_index + 1) % len(self.devices_to_broadcast)
                
                for sock in self.sockets:
                    try:
                        # 这里可以优化，消息内容缓存起来
                        message = UDPMessage.search(serial_no, self.ipv4_address)
                        sock.sendto(message, ('<broadcast>', self.broadcast_port))
                    except Exception:
                        pass
            
            time.sleep(1)  # 轮询间隔
    # def start(self):
    #     """启动广播线程"""
    #     self.broadcast_thread = Thread(target=self.broadcast_devices, daemon=True)
    #     self.broadcast_thread.start()

    def stop(self):
        """停止广播"""
        self.running = False
        if self.broadcast_thread.is_alive():
            self.broadcast_thread.join()
        for sock in self.sockets:
            sock.close()