
import binascii
from dataclasses import dataclass
import json
import struct
from typing import Any, Dict, List, Literal, Optional

from loguru import logger
from qlsdk.core.crc.crctools import crc16
from qlsdk.lse.type import CMD_DESCRIPTION, ArsLSECommand


@dataclass
class ArsLSEPacket:
    """协议帧数据结构"""
    header: bytes              # 起始标识 0x5AA5
    pkgType: int              # 包类型
    deviceType: int           # 设备类型
    deviceId: int             # 设备ID
    length: int               # 数据长度
    command: int              # 指令字
    data: bytes               # 数据载荷
    checksum: int             # 校验和
    
    def __str__(self):
        hex_data = binascii.hexlify(self.data).decode() if self.data else ''
        return (f"packet(指令: {CMD_DESCRIPTION.get(self.command, '未知指令')}({hex(self.command)}), "
                f"长度: {self.length}, "
                f"数据长度: {len(self.data)}, "
                f"数据: {hex_data}, " if self.data else ""
                f"校验: 0x{self.checksum:02X})")

@dataclass
class ArsLSEResponse():
    command: int
    device_id: int
    result: int    
    time: int    
    custom_data: bytes
    
    @property
    def result_desc(self) -> str:
        if self.result is None:
            return ""
        elif self.result == 0:
            return "成功"
        else:
            return f"失败（{self.result}）"
    @property
    def command_desc(self) -> str:
        return CMD_DESCRIPTION.get(self.command, f"未知指令(0x{self.command:02X})")
 
@dataclass   
class ArsLSENotify(ArsLSEResponse):
    pass

@dataclass
class ArsLseImpedanceData(ArsLSEResponse):
    time: int
    channel: int
    data_len: int
    impedance_values: List[int]
   
@dataclass 
class ArsLseECurrentData(ArsLSEResponse):
    time: int
    channel: int
    data_len: int
    current_values: List[int]
  
'''ARS-LSE协议解析器'''
class ArsLSEParser: 
        # 协议常量
    HEADER = b'\x5A\xA5'      # 起始标识
    HEADER_LEN = 2            # 起始标识长度
    PKG_TYPE_LEN = 1
    DEVICE_TYPE_LEN = 1
    DEVICE_ID_LEN = 4
    LENGTH_POS = HEADER_LEN + PKG_TYPE_LEN + DEVICE_TYPE_LEN + DEVICE_ID_LEN  # 长度字段位置
    LENGTH_LEN = 4            # 长度字段长度
    COMMAND_LEN = 2           # 指令字段长度
    CHECKSUM_LEN = 2          # 校验和长度
    
    # 协议包固定长度+回包固定长度
    FIXED_HEADER_LEN = 16 
    FIXED_RESPONSE_LEN = 14
    
    def __init__(self):
        self.buffer = bytearray()   
        self.stats = {
            'total_packets': 0,
            'valid_packets': 0,
            'error_packets': 0,
            'last_error': None
        }
        
    def calculate_checksum(self, data: bytes) -> int:
        checksum = 0
        for byte in data:
            checksum = (checksum + byte) & 0xFF
        return checksum
    
    def parse_frame(self, raw_data: bytes) -> Optional[ArsLSEResponse]:
        """解析单帧数据"""
        try:
            # 基本长度检查
            if len(raw_data) < self.FIXED_HEADER_LEN + self.CHECKSUM_LEN:
                logger.warning(f"数据帧过短: {len(raw_data)} 字节")
                return None
            
            # 解析固定头部
            offset = 0
            
            # 1. 起始标识
            header = raw_data[offset:offset + self.HEADER_LEN]
            if header != self.HEADER:
                logger.warning(f"起始标识错误: 0x{binascii.hexlify(header).decode()}")
                return None
            offset += self.HEADER_LEN
            
            pkg_type = raw_data[offset]
            offset += self.PKG_TYPE_LEN
            
            device_type = raw_data[offset]
            offset += self.DEVICE_TYPE_LEN
            
            device_id = int.from_bytes(raw_data[offset:offset + self.DEVICE_ID_LEN], 'little')
            offset += self.DEVICE_ID_LEN
            
            # 总长度（含校验位）
            data_len = int.from_bytes(raw_data[offset:offset + self.LENGTH_LEN], 'little')
            offset += self.LENGTH_LEN
            
            # 长度校验
            if len(raw_data) < data_len:
                logger.warning(f"数据不完整，期望 {data_len} 字节数据")
                return None
            
            custom_data_len = data_len - self.FIXED_HEADER_LEN - self.FIXED_RESPONSE_LEN  # 减去固定头部和响应长度
            if custom_data_len < 0:
                logger.warning(f"数据长度异常，无法解析: {data_len} 字节")
                return None
            
            # 指令
            command = int.from_bytes(raw_data[offset:offset + self.COMMAND_LEN], 'little')
            offset += self.COMMAND_LEN
            
            # response
            result = int.from_bytes(raw_data[offset + 2 : offset + 6], 'little') 
            time = int.from_bytes(raw_data[offset + 6 : offset + 14], 'little')
            
            offset += self.FIXED_RESPONSE_LEN

            
            custom_data = None
            # 4. 可选数据部分
            if custom_data_len > 0:
                custom_data = raw_data[offset:offset + custom_data_len]
            
            offset += custom_data_len
            
            # 5. 校验和
            expected_checksum = int.from_bytes(raw_data[offset:offset + self.CHECKSUM_LEN], 'little')
    
            
            # 验证校验和
            # 计算范围：除了校验位以外的所有字节（从起始标识开始到数据末尾）
            check_data = raw_data[0:offset]
            crc16_checksum = crc16(check_data)
            
            if expected_checksum != crc16_checksum:
                logger.warning(f"校验和错误: 期望 0x{expected_checksum:02X}, "
                             f"实际 0x{crc16_checksum:02X}")
                return None
            
            # 创建帧对象
            packet = ArsLSEResponse(
                device_id=device_id,
                command=command,
                result=result,
                time = time,
                custom_data=custom_data,
            )
            
            self.stats['valid_packets'] += 1
            return packet
            
        except struct.error as e:
            logger.error(f"结构解析错误: {e}")
            self.stats['error_packets'] += 1
            self.stats['last_error'] = str(e)
        except Exception as e:
            logger.error(f"解析异常: {e}")
            self.stats['error_packets'] += 1
            self.stats['last_error'] = str(e)
        
        return None
    
    def feed_data(self, data: bytes) -> List[ArsLSEResponse]:
        """
        喂入数据，返回解析出的完整帧列表
        支持粘包、半包处理
        """
        frames = []
        
        # 将新数据添加到缓冲区
        self.buffer.extend(data)
        self.stats['total_packets'] += 1
        
        while len(self.buffer) > 0:
            # 1. 查找起始标识
            header_pos = self.buffer.find(self.HEADER)
            if header_pos == -1:
                # 没有找到起始标识，清空缓冲区
                # self.buffer.clear() // 也可以选择丢弃前面部分数据，保留最后几个字节以防止起始标识被分割
                if len(self.buffer) > self.HEADER_LEN:
                    logger.debug(self.buffer.hex())
                    logger.warning(f"未找到起始标识，丢弃 {len(self.buffer) - self.HEADER_LEN} 字节数据")
                    del self.buffer[:-self.HEADER_LEN]  # 保留最后几个字节
                break
            
            # 移除起始标识前的无效数据
            if header_pos > 0:
                discarded = bytes(self.buffer[:header_pos])
                logger.debug(f"丢弃无效数据: {binascii.hexlify(discarded).decode()}")
                del self.buffer[:header_pos]
                continue
            
            # 2. 检查是否有足够数据解析长度字段
            if len(self.buffer) < self.FIXED_HEADER_LEN:
                # 数据不足，等待更多数据
                break
            
            # 3. 解析数据长度
            total_len = int.from_bytes(self.buffer[self.LENGTH_POS:self.LENGTH_POS + self.LENGTH_LEN], 'little')
            
            # 5. 检查是否收到完整帧
            if len(self.buffer) < total_len:
                # 数据不完整，等待更多数据
                break
            
            # 6. 提取并解析完整帧
            frame_data = bytes(self.buffer[:total_len])
            frame = self.parse_frame(frame_data)
            
            if frame:
                frames.append(frame)
                # logger.info(f"成功解析: {frame}")
            else:
                logger.warning(f"解析失败，丢弃数据: {binascii.hexlify(frame_data).decode()}")
            
            # 7. 从缓冲区移除已处理的数据
            del self.buffer[:total_len]
        
        return frames
    
    def create_frame(self, command: int, data: bytes = b'') -> bytes:
        """创建协议帧"""
        # 构造数据部分
        command_byte = bytes([command])
        data_with_command = command_byte + data
        
        # 长度 = 指令字长度 + 数据长度
        length = len(data_with_command)
        
        # 构造待校验数据
        check_data = struct.pack('>HB', length, command) + data
        
        # 计算校验和
        checksum = self.calculate_checksum(check_data)
        
        # 组装完整帧
        frame = (self.HEADER + 
                struct.pack('>H', length) +
                command_byte +
                data +
                bytes([checksum]))
        
        return frame
    
    def get_stats(self) -> Dict[str, Any]:
        """获取解析统计信息"""
        return self.stats.copy()

class Message(object):
    def __init__(self, cmd: ArsLSECommand, data: bytes = b""):
        self.cmd = cmd
        self.data = data

class ArsLSEMessage(object):
    PKG_START = 0xA55A
    HEADER_LEN = 14
    
    @classmethod
    def _pack(cls, cmd: int, data: bytes = b"") -> bytes:    
        header = cls._pack_header(cmd, len(data))
        payload = header + data
        return payload + cls.checksum(payload)
    
    @classmethod
    def _pack_header(cls, cmd:int, data_len: int) -> bytes:
        return (
            ArsLSEMessage.PKG_START.to_bytes(2, 'little')
            + 0x0.to_bytes(6, 'little')  # 保留6字节
            + (ArsLSEMessage.HEADER_LEN + data_len + 2).to_bytes(4, 'little')  # +1 for checksum
            + cmd.to_bytes(2, 'little')
        )
    @staticmethod
    def checksum(data: bytes) -> bytes:
        return crc16(data).to_bytes(2, 'little')
    
    @staticmethod
    def handshake(mode:Literal[0, 1]=0):
        reserved = 0
        body = reserved.to_bytes(2, 'little')
        body += mode.to_bytes(4, 'little') # 固定1
        return ArsLSEMessage._pack(ArsLSECommand.HAND_SHAKE.value, body)
    
    # 准备刺激、使用界面参数开始刺激、停止界面参数刺激的指令
    @staticmethod
    def ready_for_ui_stim():
        return ArsLSEMessage._pack(ArsLSECommand.READY_FOR_UI_STIM.value)
    
    # 开始刺激--使用界面参数
    @staticmethod
    def start_ui_stim():
        return ArsLSEMessage._pack(ArsLSECommand.START_UI_STIM.value)
    
    #停止刺激--使用界面参数
    @staticmethod
    def stop_ui_stim():
        return ArsLSEMessage._pack(ArsLSECommand.STOP_UI_STIM.value)

    @staticmethod
    def start_e_stim(config: List, mode:Literal[0, 1, 2] = 0):
        if config is None or len(config) == 0:
            raise ValueError("电刺激配置不能为空")
        
        # mode = 2 # 暂不开启配置模式，直接用默认参数刺激
        body = mode.to_bytes(2, 'little')
        json_cnt = len(config)
        body += json_cnt.to_bytes(4, 'little') #  配置个数
        body += (0).to_bytes(4, 'little') # 保留4字节
        for cfg in config:
            b_cfg = json.dumps(cfg).encode('utf-8') + b'\x00'
            body += (len(b_cfg)).to_bytes(4, 'little') # json长度
            body += b_cfg

        return ArsLSEMessage._pack(ArsLSECommand.START_E_STIM.value, body)
    
    @staticmethod
    def stop_e_stim():
        return ArsLSEMessage._pack(ArsLSECommand.STOP_E_STIM.value)
    
    @staticmethod
    def start_light_sound_stim(config: List, mode:Literal[0, 1, 2] = 0):
        if config is None or len(config) == 0:
            raise ValueError("声光刺激配置不能为空")
        
        # mode = 2 # 暂不开启配置模式，直接用默认参数刺激
        body = mode.to_bytes(2, 'little')
        body += (0).to_bytes(4, 'little') # 保留4字节
        json_cnt = len(config)
        body += json_cnt.to_bytes(4, 'little') #  配置个数
        for cfg in config:
            b_cfg = json.dumps(cfg).encode('utf-8') 
            body += (len(b_cfg)).to_bytes(4, 'little') # json长度
            body += b_cfg
        
        print(body)    
        return ArsLSEMessage._pack(ArsLSECommand.CONFIG_AL_STIM.value, body)
    
    @staticmethod
    def stop_light_sound_stim():
        return ArsLSEMessage._pack(ArsLSECommand.STOP_AL_STIM.value)
    
    @staticmethod
    def start_impedance_measure():
        return ArsLSEMessage._pack(ArsLSECommand.START_IMPEDANCE.value)
    
    @staticmethod
    def stop_impedance_measure():
        return ArsLSEMessage._pack(ArsLSECommand.STOP_IMPEDANCE.value)
    
    @staticmethod
    def get_audio_list():
        return ArsLSEMessage._pack(ArsLSECommand.GET_AUDIO_LIST.value)