'''


'''

# 基礎功能的 import
import asyncio
import serial_asyncio

import os
import sys
import serial
import signal
import time
import threading
import struct
from enum import Enum, auto

# # XBee 模組
# from xbee.frame import APIFrame

# 自定義的 import
from .utils import setup_logger

# ====================== 分割線 =====================

logger = setup_logger(os.path.basename(__file__))

# ====================== 分割線 =====================
class XBeeFrameHandler:
    """XBee API Frame 處理器"""
    
    @staticmethod
    def parse_at_command_response(frame: bytes) -> dict:
        """解析 AT Command Response (0x88)"""
        if len(frame) < 8:
            return None
        
        frame_type = frame[3]
        if frame_type != 0x88:
            return None
        
        frame_id = frame[4]
        at_command = frame[5:7]
        status = frame[7]
        data = frame[8:] if len(frame) > 8 else b''
        
        return {
            'frame_id': frame_id,
            'command': at_command,
            'status': status,
            'data': data,
            'is_ok': status == 0x00
        }
    
    @staticmethod
    def parse_receive_packet(frame: bytes) -> dict:
        # """解析 RX Packet (0x90) - 未來擴展用"""
        # if len(frame) < 15 or frame[3] != 0x90:
        #     return None
        
        # return {
        #     'source_addr': frame[4:12],
        #     'reserved': frame[12:14],
        #     'options': frame[14],
        #     'data': frame[15:-1]
        # }
        pass
        return None

    @staticmethod
    def encapsulate_data(data: bytes, dest_addr64: bytes = b'\x00\x00\x00\x00\x00\x00\xFF\xFF', frame_id = 0x01) -> bytes:
        """
        將數據封裝為 XBee API 傳輸幀
        
        使用 XBee API 格式封裝數據:
        - 傳輸請求幀 (0x10)
        - 使用廣播地址
        - 添加適當的頭部和校驗和
        """
        frame_type = 0x10
        dest_addr16 = b'\xFF\xFE'
        broadcast_radius = 0x00
        options = 0x00

        frame = struct.pack(">B", frame_type) + struct.pack(">B", frame_id)
        frame += dest_addr64 + dest_addr16
        frame += struct.pack(">BB", broadcast_radius, options) + data
        checksum = 0xFF - (sum(frame) & 0xFF)
        return b'\x7E' + struct.pack(">H", len(frame)) + frame + struct.pack("B", checksum)

    @staticmethod
    def decapsulate_data(data: bytes):
        # 這裡可以根據需要進行數據解封裝

        # XBee API 幀格式: 
        # 起始分隔符(1字節) + 長度(2字節) + API標識符(1字節) + 數據 + 校驗和(1字節)
        
        # 檢查幀起始符 (0x7E)
        if not data or len(data) < 5 or data[0] != 0x7E:
            return data
            
        # 獲取數據長度 (不包括校驗和)
        # length = (data[1] << 8) + data[2]
        length = (data[1] << 8) | data[2]
        
        # 檢查幀完整性
        if len(data) < length + 4:  # 起始符 + 長度(2字節) + 數據 + 校驗和
            return data
            
        # 提取API標識符和數據
        frame_type = data[3]
        # frame_data = data[4:4+length-1]  # 減1是因為API標識符已經算在長度中
        
        # 根據不同的幀類型進行處理
        if frame_type == 0x90:  # 例如，這是"接收數據包"類型
            rf_data_start = 3 + 12
            return data[rf_data_start:3 + length]
        else:
            return None          
        return data


class ATCommandHandler:
    """AT 指令回應處理器"""
    
    def __init__(self, serial_port: str):
        self.serial_port = serial_port
        self.handlers = {
            b'DB': self._handle_rssi,
            b'SH': self._handle_serial_high,
            b'SL': self._handle_serial_low,
            # 可擴展其他 AT 指令
        }
    
    def handle_response(self, response: dict):
        """根據 AT 指令類型分派處理"""
        if not response or not response['is_ok']:
            if response:
                print(f"[{self.serial_port}] AT {response['command'].decode()} 失敗，狀態碼: {response['status']}")
            return
        
        command = response['command']
        handler = self.handlers.get(command)
        
        if handler:
            handler(response['data'])
        else:
            print(f"[{self.serial_port}] 未處理的 AT 指令: {command.decode()}")
    
    def _handle_rssi(self, data: bytes):
        """處理 DB (RSSI) 回應"""
        if not data:
            return
        
        rssi_value = data[0]
        now = time.time()
        
        # 檢查是否最近有收到 MAVLink
        last_mavlink_time = serial_last_mavlink_time.get(self.serial_port, 0)
        if now - last_mavlink_time > 0.5:
            print(f"[{self.serial_port}] 超過 0.5 秒未接收 MAVLink，RSSI = -{rssi_value} dBm 已忽略")
            return
        
        # 取得對應的 sysid
        sysid = serial_to_sysid.get(self.serial_port)
        if sysid is None:
            print(f"[{self.serial_port}] 找不到 sysid 對應，RSSI = -{rssi_value} dBm，已忽略")
            return
        
        # 記錄 RSSI
        rssi_history[sysid].append(-rssi_value)
        time_history[sysid].append(now)
        # print(f"[SYSID:{sysid}] RSSI = -{rssi_value} dBm")
    
    def _handle_serial_high(self, data: bytes):
        # """處理 SH (Serial Number High) - 範例"""
        # if len(data) >= 4:
        #     serial_high = int.from_bytes(data[:4], 'big')
        #     print(f"[{self.serial_port}] Serial High: 0x{serial_high:08X}")
        pass
    
    def _handle_serial_low(self, data: bytes):
        # """處理 SL (Serial Number Low) - 範例"""
        # if len(data) >= 4:
        #     serial_low = int.from_bytes(data[:4], 'big')
        #     print(f"[{self.serial_port}] Serial Low: 0x{serial_low:08X}")
        pass

# ====================== 分割線 =====================

class SerialHandler(asyncio.Protocol): # asyncio.Protocol 用於處理 Serial 收發
    def __init__(self, udp_handler, serial_port_str):
        self.udp_handler = udp_handler # UDP 的傳輸物件
        self.serial_port_str = serial_port_str
        self.at_handler = ATCommandHandler(serial_port_str)

        self.buffer = bytearray()  # 用於緩存接收到的資料
        self.transport = None # Serial 自己的傳輸物件
        # self.first_data = True  # 標記是否為第一次收到資料
        # self.has_processed = False # 測試模式用 處理數據旗標 # debug

    def connection_made(self, transport):
        self.transport = transport
        if hasattr(self.udp_handler, 'set_serial_handler'):
            self.udp_handler.set_serial_handler(self)
        # logger.info(f"Serial port {self.serial_port_str} connected.") # debug

    # Serial 收到資料的處理過程
    def data_received(self, data):
        # 1. 把收到的資料加入緩衝區
        self.buffer.extend(data) 

        # 2. 需要完整的 header 才能解析
        while len(self.buffer) >= 3:
            # 3. 瞄準 XBee API Frame (0x7E 開頭的封包) 
            if self.buffer[0] != 0x7E:
                self.buffer.pop(0) # 如果不是就丟掉
                continue

            # 4. 讀取 payload 長度
            length = (self.buffer[1] << 8) | self.buffer[2]
            full_length = 3 + length + 1

            # 5. 等待完整封包
            if len(self.buffer) < full_length:
                break

            # 6. 提取完整 frame 並從緩衝區移除
            an_frame = self.buffer[:full_length]
            del self.buffer[:full_length]

            # 7. 判斷 frame 類型
            frame_type = an_frame[3]
            
            if frame_type == 0x88:
                # 處理 AT Command 回應
                # response = XBeeFrameHandler.parse_at_command_response(an_frame)
                # self.at_handler.handle_response(response)
                pass
                
            elif frame_type == 0x90:
                # Receive Packet (RX) payload 先解碼 
                processed_data = XBeeFrameHandler.decapsulate_data(bytes(an_frame))
                # 轉換失敗就捨棄了
                if processed_data is None: 
                    continue
                # 再透過 UDP 送出
                self.udp_handler.transport.sendto(processed_data, (self.udp_handler.LOCAL_HOST_IP, self.udp_handler.target_port))

            elif frame_type == 0x8B:
                pass

            else:
                # 其他類型的 frame 未來可擴展處理 現在忽略
                logger.warning(f"[{self.serial_port_str}] Undefined frame type: 0x{frame_type:02X}")

            # # RSSI
            # if frame[3] == 0x88 and frame[5:7] == b'DB': # frame[3] == 0x88 AT -> API 封包
            #                                              # frame[5:7] == b'DB' -> API 封包的DB參數
            #     status = frame[7]                        # 
            #     if status == 0x00 and len(frame) > 8:    # status == 0x00 -> 這個封包是有效封包
            #         rssi_value = frame[8]
            #         now = time.time()

            #         # === 優化 1：僅信任最近 0.5 秒內有接收 MAVLink 的 port
            #         last_time = serial_last_mavlink_time.get(self.serial_port, 0)
            #         if now - last_time <= 0.5:
            #             sysid = serial_to_sysid.get(self.serial_port, None)
            #             if sysid is not None:
            #                 rssi_history[sysid].append(-rssi_value)
            #                 time_history[sysid].append(now)
            #                 # print(f"[SYSID:{sysid}] RSSI = -{rssi_value} dBm")
            #             else:
            #                 print(f"[{self.serial_port}] 找不到 sysid 對應，RSSI = -{rssi_value} dBm，已忽略")
            #         else:
            #             print(f"[{self.serial_port}] 超過 0.5 秒未接收 MAVLink，RSSI = -{rssi_value} dBm 已忽略")
            #     else:
            #         print(f"[{self.serial_port}] DB 指令失敗，狀態碼: {status}")
        

class UDPHandler(asyncio.DatagramProtocol): # asyncio.DatagramProtocol 用於處理 UDP 收發
    
    LOCAL_HOST_IP = '127.0.0.1'  # 只送給本地端IP

    def __init__(self, target_port):
        self.target_port = target_port # 目標 UDP 端口

        self.serial_handler = None # Serial 的傳輸物件
        self.transport = None # UDP 自己的傳輸物件
        self.remote_addr = None  # 儲存動態獲取的遠程地址 # debug
        # self.has_processed = False # 測試模式用 處理數據旗標 # debug

    def connection_made(self, transport):
        self.transport = transport
        # logger.info(f"UDP transport ready. Waiting for serial data before sending.") # debug

    def set_serial_handler(self, serial_handler):
        self.serial_handler = serial_handler

    # UDP 收到資料的處理過程
    def datagram_received(self, data, addr):
        # 儲存對方的地址（這樣就能向同一個來源回傳數據）
        # self.remote_addr = addr # debug
        # print(f"Received UDP data from {addr}, setting as remote address")
        
        processed_data = XBeeFrameHandler.encapsulate_data(data)

        if self.serial_handler:
            self.serial_handler.transport.write(processed_data)
    
#==================================================================

class SerialReceiverType(Enum):
    """連接類型"""
    TELEMETRY = auto() 
    XBEEAPI2AT = auto() 
    OTHER = auto() 


class serial_manager:

    class serial_object:
        def __init__(self, serial_port, baudrate, target_port, receiver_type: SerialReceiverType):
            self.serial_port = serial_port # /dev/ttyUSB or COM3 ...etc
            self.baudrate = baudrate
            self.receiver_type = receiver_type
            self.target_port = target_port # 指向的 UPD 端口

            self.transport = None # TODO 這個變數可能沒有作用
            self.protocol = None # TODO 這個變數可能沒有作用
            self.udp_handler = None
            self.serial_handler = None

    def __init__(self):
        self.thread = None
        self.loop = None
        self.running = False
        self.serial_count = 0
        self.serial_objects = {} # serial id num : serial_object

    def __del__(self):
        self.loop = None
        self.thread = None

    def start(self):

        if self.running:
            logger.warning("serial_manager already running")
            return
        
        self.running = True

        # 啟動獨立線程 命名為 SerialManager
        self.thread = threading.Thread(
            target=self._run_event_loop, 
            name="SerialManager"
        )
        self.thread.daemon = False  # 不設為 daemon，確保正確關閉
        self.thread.start()

        # 等待 _run_event_loop 建立事件循環的物件 self.loop
        start_timeout = 2.0
        start_time = time.time()
        while not self.loop and time.time() - start_time < start_timeout:
            time.sleep(0.1)

        # 檢查另一個執行緒有沒有成功建立事件循環物件 self.loop
        if self.loop:
            logger.info("serial_manager thread started <-")
            return True
        else:
            logger.error("serial_manager failed to start")
            return False

    def shutdown(self):
        """停止 serial_manager 和其管理的所有 serial_object"""
        # 自己在 running 狀態下才執行停止程序
        if not self.running:
            logger.warning("serial_manager is not running")
            return

        # 停止所有被管理的 serial_object
        for serial_id in list(self.serial_objects.keys()):
            self.remove_serial_link(serial_id)

        # 停止自己
        self.running = False

        # 解開事件循環的阻塞
        if self.loop:
            self.loop.call_soon_threadsafe(self.loop.stop)

        # 等待線程結束
        if self.thread and self.thread.is_alive():
            self.thread.join(timeout=5.0)
            if self.thread.is_alive():
                logger.warning("serial_manager thread did not stop gracefully")
        
        logger.info("serial_manager thread END!")

    def _run_event_loop(self):
        """在獨立線程中運行 asyncio 事件循環"""
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        # # 為每個 serial_object 建立連接
        # for serial_obj in self.serial_objects:
        #     coro = serial_asyncio.create_serial_connection(
        #         self.loop,
        #         lambda: SerialProtocol(serial_obj.receiver_type),
        #         serial_obj.serial_port,
        #         baudrate=serial_obj.baudrate
        #     )
        #     transport, protocol = self.loop.run_until_complete(coro)
        #     serial_obj.transport = transport
        #     serial_obj.protocol = protocol

        try:
            self.loop.run_forever()
        finally:
            self.loop.close()

    def create_serial_link(self, serial_port, baudrate, target_port, receiver_type: SerialReceiverType):

        if not self.running or not self.loop:
            logger.error("Event loop not running, cannot create serial link")
            return False

        # 檢查 serial port 有效
        if not self.check_serial_port(serial_port, baudrate):
            logger.error(f"Serial port {serial_port} validation failed")
            return False

        # 使用 run_coroutine_threadsafe 執行協程並獲取結果
        future = asyncio.run_coroutine_threadsafe(
            self._async_create_serial_link(serial_port, baudrate, target_port, receiver_type),
            self.loop
        )

        try:
            # 等待結果，設定合理的超時時間
            result = future.result(timeout=5.0)
            if result:
                logger.info(f"Create Serial Link: {serial_port} -> UDP {target_port}")
            return True
        except asyncio.TimeoutError:
            logger.error(f"Timeout creating serial link for {serial_port}")
            return False
        except Exception as e:
            logger.error(f"Failed to create serial link for {serial_port}: {e}")
            return False

    async def _async_create_serial_link(self, serial_port, baudrate, target_port, receiver_type: SerialReceiverType):
        """在事件循環線程中執行實際的連接創建"""
        try:
            # 創建 serial_object 實例
            serial_obj = self.serial_object(serial_port, baudrate, target_port, receiver_type)

            # 建立 UDP 處理器並指定目標端口位置
            serial_obj.udp_handler = UDPHandler(target_port)
            
            # 建立 UDP 傳輸，不指定接收端口(自己)，讓系統自動分配
            udp_transport, udp_protocol = await self.loop.create_datagram_endpoint(
                lambda: serial_obj.udp_handler,
                local_addr=('0.0.0.0', 0)  # 使用端口 0 讓系統自動分配可用端口
            )
            serial_obj.transport = udp_transport
            serial_obj.protocol = udp_protocol
            
            # logger.info(f"UDP endpoint created for {serial_port}") # debug

            # 建立 Serial 處理器，將 UDP 處理器傳給它
            serial_obj.serial_handler = SerialHandler(serial_obj.udp_handler, serial_port)

            # 建立 Serial 連接
            serial_transport, _ = await serial_asyncio.create_serial_connection(
                self.loop, 
                lambda: serial_obj.serial_handler, 
                serial_port, 
                baudrate=baudrate
            )
            
            # logger.info(f"Serial connection created for {serial_port}") # debug

            # 將 serial_object 加入管理列表
            serial_id = self.serial_count + 1
            self.serial_objects[serial_id] = serial_obj
            self.serial_count += 1
            
            # logger.info(f"Serial object {serial_id} added to manager") # debug
            return True
            
        except Exception as e:
            logger.error(f"Exception in _async_create_serial_link for {serial_port}: {str(e)}")
            # 清理已創建的資源
            if 'serial_obj' in locals():
                if hasattr(serial_obj, 'transport') and serial_obj.transport:
                    serial_obj.transport.close()
            return False

    def remove_serial_link(self, serial_id):
        """移除串口連接（線程安全方式）"""
        # 確保事件循環正在運行
        if not self.loop:
            logger.error("Event loop not running")
            return False
        
        # 檢查 serial_id 是否存在
        if serial_id not in self.serial_objects:
            logger.warning(f"Serial object {serial_id} not found")
            return False

        # 使用 run_coroutine_threadsafe 執行協程
        future = asyncio.run_coroutine_threadsafe(
            self._async_remove_serial_link(serial_id),
            self.loop
        )
        
        try:
            result = future.result(timeout=3.0)
            if result:
                logger.info(f"Remove Serial Link {serial_id}")
            return result
        except asyncio.TimeoutError:
            logger.error(f"Timeout removing serial link {serial_id}")
            return False
        except Exception as e:
            logger.error(f"Failed to remove serial link {serial_id}: {e}")
            return False

    async def _async_remove_serial_link(self, serial_id):
        """在事件循環線程中執行實際的連接移除"""
        if serial_id not in self.serial_objects:
            logger.warning(f"Serial object {serial_id} not in managed list")
            return False
        
        try:
            serial_obj = self.serial_objects[serial_id]
            
            # 關閉 UDP transport
            if hasattr(serial_obj, 'transport') and serial_obj.transport:
                serial_obj.transport.close()
            
            # 關閉 Serial transport
            if hasattr(serial_obj, 'serial_handler') and serial_obj.serial_handler:
                if hasattr(serial_obj.serial_handler, 'transport') and serial_obj.serial_handler.transport:
                    serial_obj.serial_handler.transport.close()
            
            # 從管理列表中移除
            del self.serial_objects[serial_id]
            # logger.info(f"Serial object {serial_id} removed from manager") # debug
            return True
            
        except Exception as e:
            logger.error(f"Exception in _async_remove_serial_link for {serial_id}: {str(e)}")
            return False

    def get_serial_link(self):
        ret = {} # serial id num : serial_port string
        for key, obj in self.serial_objects.items():
            ret[key] = obj.serial_port
        return ret

    @staticmethod
    def check_serial_port(serial_port, baudrate):
        """檢查串口是否存在與可用"""
        # 檢查設備是否存在
        if not os.path.exists(serial_port):
            logger.error(f"Serial Device {serial_port} Not Found")
            return False
            
        # 檢查是否有權限訪問設備
        try:
            if not os.access(serial_port, os.R_OK | os.W_OK):
                logger.error(f"No permission to access {serial_port}")
                return False
        except Exception as e:
            logger.error(f"Cannot Access Serial Device {serial_port}: {str(e)}")
            return False
        
        # 檢查是否被占用
        try:
            # 嘗試打開串口
            ser = serial.Serial(serial_port, baudrate)
            ser.close()  # 打開成功後立即關閉
            return True
        except serial.SerialException as e:
            logger.error(f"Serial Device {serial_port} is Occupied or Inaccessible: {str(e)}")
            return False
        except Exception as e:
            logger.error(f"Unknown Error: {str(e)}")
            return False


if __name__ == '__main__':
    sm = serial_manager()
    sm.start()

    SERIAL_PORT = '/dev/ttyUSB0'  # 手動指定
    SERIAL_BAUDRATE = 115200
    UDP_REMOTE_PORT = 14571
    sm.create_serial_link(SERIAL_PORT, SERIAL_BAUDRATE, UDP_REMOTE_PORT, SerialReceiverType.XBEEAPI2AT)

    linked_serial = sm.get_serial_link()
    print(linked_serial)
    time.sleep(10)

    sm.remove_serial_link(1)
    time.sleep(3)
    sm.shutdown()