"""
L1报文解析服务 — UDP协议

假设报文格式（固定帧结构，收到实际协议文档后对应调整）：
┌─────────────────────────────────────────────────────┐
│  Offset  Size  说明                                  │
│  0       2B    魔数  0xAA 0xBB                       │
│  2       4B    报文类型  ASCII，如 "PC01"             │
│  6       2B    序列号  uint16 大端                   │
│  8       2B    Body长度  uint16 大端                 │
│  10      2B    校验和  所有Body字节累加低16位         │
│  12      N B   Body  GBK编码固定列宽文本             │
└─────────────────────────────────────────────────────┘
UDP最大包：65507字节，单帧不分片。
回执帧：Header相同结构，Body = "ACK" + 原序列号(2B)
"""
import asyncio
import struct
import time
import uuid
import json
from datetime import datetime
from typing import Optional, Dict, Any, Tuple
from loguru import logger

from app.config import settings


# ─────────────────────────── 报文类型注册表 ───────────────────────────
MSG_TYPES: Dict[str, str] = {
    "PC01": "卷材入口报文",
    "PC02": "卷材出口报文",
    "PC03": "过程数据报文",
    "PC04": "质量数据报文",
    "PC05": "设备状态报文",
    "PC10": "计划下发报文",
    "PC11": "计划确认报文",
    "PC20": "心跳报文",
}

HEADER_SIZE = 12  # 报文头固定长度
MAGIC = b'\xAA\xBB'


# ─────────────────────────── 校验和 ───────────────────────────
def _checksum(body: bytes) -> int:
    return sum(body) & 0xFFFF


# ─────────────────────────── 报文头解析 ───────────────────────────
def parse_header(raw: bytes) -> Optional[Dict[str, Any]]:
    if len(raw) < HEADER_SIZE:
        logger.warning(f"报文过短: {len(raw)}B，丢弃")
        return None
    magic = raw[0:2]
    if magic != MAGIC:
        logger.warning(f"魔数错误: {magic.hex()}，丢弃")
        return None
    msg_type = raw[2:6].decode("ascii", errors="replace").strip()
    seq = struct.unpack(">H", raw[6:8])[0]
    body_len = struct.unpack(">H", raw[8:10])[0]
    checksum = struct.unpack(">H", raw[10:12])[0]
    body = raw[HEADER_SIZE: HEADER_SIZE + body_len]
    if len(body) < body_len:
        logger.warning(f"Body不完整: 期望{body_len}B 实际{len(body)}B")
        return None
    if _checksum(body) != checksum:
        logger.warning(f"校验和错误 [{msg_type}] seq={seq}，丢弃")
        return None
    return {"msg_type": msg_type, "seq": seq, "body": body,
            "body_str": body.decode("gbk", errors="replace")}


# ─────────────────────────── 构建回执帧 ───────────────────────────
def build_ack(seq: int) -> bytes:
    body = b"ACK" + struct.pack(">H", seq)
    hdr = MAGIC
    hdr += b"ACK ".ljust(4)[:4]
    hdr += struct.pack(">H", seq)
    hdr += struct.pack(">H", len(body))
    hdr += struct.pack(">H", _checksum(body))
    return hdr + body


# ─────────────────────────── 构建发送帧 ───────────────────────────
def build_frame(msg_type: str, body: bytes, seq: int = 0) -> bytes:
    hdr = MAGIC
    hdr += msg_type.encode("ascii").ljust(4)[:4]
    hdr += struct.pack(">H", seq)
    hdr += struct.pack(">H", len(body))
    hdr += struct.pack(">H", _checksum(body))
    return hdr + body


# ─────────────────────────── Body解析器 ───────────────────────────
class PC01Parser:
    """卷材入口报文
    Body固定列宽(GBK): 卷号(20) 钢种(10) 厚度(6) 宽度(6) 重量(8) 班次(2)
    """
    def parse(self, body: str) -> Dict[str, Any]:
        return {
            "coil_no":     body[0:20].strip(),
            "steel_grade": body[20:30].strip(),
            "thickness":   _safe_float(body[30:36]),
            "width":       _safe_float(body[36:42]),
            "weight":      _safe_float(body[42:50]),
            "shift":       body[50:52].strip(),
            "event_time":  datetime.now(),
        }


class PC02Parser:
    """卷材出口报文
    Body: 卷号(20) 实测厚度(6) 实测宽度(6) 处理长度(8) 平均速度(6) 质量等级(2)
    """
    def parse(self, body: str) -> Dict[str, Any]:
        return {
            "coil_no":          body[0:20].strip(),
            "actual_thickness": _safe_float(body[20:26]),
            "actual_width":     _safe_float(body[26:32]),
            "process_length":   _safe_float(body[32:40]),
            "avg_speed":        _safe_float(body[40:46]),
            "quality_grade":    body[46:48].strip(),
            "event_time":       datetime.now(),
        }


class PC03Parser:
    """过程数据报文（周期推送）
    Body: 卷号(20) 位置(10) 速度(6) 入口张力(8) 出口张力(8) 酸液温度(6)
    """
    def parse(self, body: str) -> Dict[str, Any]:
        return {
            "coil_no":        body[0:20].strip(),
            "position":       body[20:30].strip(),
            "speed":          _safe_float(body[30:36]),
            "tension_inlet":  _safe_float(body[36:44]),
            "tension_outlet": _safe_float(body[44:52]),
            "acid_temp":      _safe_float(body[52:58]),
            "event_time":     datetime.now(),
        }


class PC04Parser:
    """质量数据报文
    Body: 卷号(20) 缺陷类型(10) 缺陷位置(8) 严重程度(2)
    """
    def parse(self, body: str) -> Dict[str, Any]:
        return {
            "coil_no":      body[0:20].strip(),
            "defect_type":  body[20:30].strip(),
            "defect_pos":   _safe_float(body[30:38]),
            "severity":     body[38:40].strip(),
            "event_time":   datetime.now(),
        }


class PC05Parser:
    """设备状态报文
    Body: 设备编号(10) 状态码(4) 故障码(6) 时间戳(14 yyyyMMddHHmmss)
    """
    def parse(self, body: str) -> Dict[str, Any]:
        ts_str = body[20:34].strip()
        try:
            ts = datetime.strptime(ts_str, "%Y%m%d%H%M%S")
        except Exception:
            ts = datetime.now()
        return {
            "equipment_code": body[0:10].strip(),
            "status_code":    body[10:14].strip(),
            "fault_code":     body[14:20].strip(),
            "event_time":     ts,
        }


class PC20Parser:
    """心跳报文 Body: 时间戳(14)"""
    def parse(self, body: str) -> Dict[str, Any]:
        return {"event_time": datetime.now(), "raw_ts": body[0:14].strip()}


def _safe_float(s: str) -> float:
    try:
        return float(s.strip())
    except (ValueError, AttributeError):
        return 0.0


BODY_PARSERS: Dict[str, Any] = {
    "PC01": PC01Parser(),
    "PC02": PC02Parser(),
    "PC03": PC03Parser(),
    "PC04": PC04Parser(),
    "PC05": PC05Parser(),
    "PC20": PC20Parser(),
}


# ─────────────────────────── 分发器 ───────────────────────────
class MessageDispatcher:
    def __init__(self):
        self._handlers: Dict[str, list] = {}

    def register(self, msg_type: str):
        def decorator(func):
            self._handlers.setdefault(msg_type, []).append(func)
            return func
        return decorator

    async def dispatch(self, msg_type: str, data: Dict[str, Any]):
        for handler in self._handlers.get(msg_type, []):
            try:
                await handler(data)
            except Exception as e:
                logger.error(f"报文处理器异常 [{msg_type}]: {e}")


dispatcher = MessageDispatcher()


# ─────────────────────────── UDP 服务端 ───────────────────────────
class L1UdpProtocol(asyncio.DatagramProtocol):
    """asyncio UDP DatagramProtocol 实现"""

    def __init__(self, server: "L1UdpServer"):
        self._server = server
        self.transport: Optional[asyncio.DatagramTransport] = None

    def connection_made(self, transport: asyncio.DatagramTransport):
        self.transport = transport
        host, port = transport.get_extra_info("sockname")
        logger.info(f"UDP监听启动: {host}:{port}")

    def datagram_received(self, data: bytes, addr: Tuple[str, int]):
        asyncio.create_task(self._server.handle(data, addr, self.transport))

    def error_received(self, exc: Exception):
        logger.error(f"UDP错误: {exc}")

    def connection_lost(self, exc):
        logger.warning(f"UDP连接丢失: {exc}")


class L1UdpServer:
    """UDP报文接收服务"""

    def __init__(self):
        self._transport: Optional[asyncio.DatagramTransport] = None
        self._running = False
        # 统计
        self.recv_count = 0
        self.error_count = 0

    async def start(self):
        self._running = True
        loop = asyncio.get_running_loop()
        self._transport, _ = await loop.create_datagram_endpoint(
            lambda: L1UdpProtocol(self),
            local_addr=(settings.L1_HOST, settings.L1_PORT),
        )
        logger.info(f"L1 UDP服务已启动，监听 {settings.L1_HOST}:{settings.L1_PORT}")

    async def handle(self, raw: bytes, addr: Tuple[str, int],
                     transport: asyncio.DatagramTransport):
        t0 = time.time()
        self.recv_count += 1
        logger.debug(f"收到UDP包 from {addr[0]}:{addr[1]}  {len(raw)}B")

        header = parse_header(raw)
        if not header:
            self.error_count += 1
            await self._save_log(raw, addr, None, "error", "报文头解析失败", t0)
            return

        msg_type = header["msg_type"]
        seq = header["seq"]

        # 发送ACK回执
        if msg_type != "ACK":
            ack = build_ack(seq)
            transport.sendto(ack, addr)

        # 心跳不做业务处理
        if msg_type == "PC20":
            logger.debug(f"心跳 seq={seq}")
            return

        # Body解析
        body_parser = BODY_PARSERS.get(msg_type)
        data: Dict[str, Any] = {}
        if body_parser:
            try:
                data = body_parser.parse(header["body_str"])
            except Exception as e:
                logger.error(f"Body解析异常 [{msg_type}]: {e}")
                self.error_count += 1
                await self._save_log(raw, addr, header, "error", str(e), t0)
                return
        else:
            logger.warning(f"未知报文类型: {msg_type}")

        elapsed_ms = (time.time() - t0) * 1000
        logger.info(f"[{msg_type}] seq={seq} from {addr[0]} 耗时{elapsed_ms:.1f}ms")

        await self._save_log(raw, addr, header, "success", None, t0, data)
        await dispatcher.dispatch(msg_type, data)

    async def _save_log(self, raw: bytes, addr: Tuple[str, int],
                        header: Optional[Dict], status: str,
                        error_msg: Optional[str], t0: float,
                        parsed_data: Optional[Dict] = None):
        try:
            from app.database import AsyncSessionLocal
            from app.models.message import MessageLog
            elapsed_ms = (time.time() - t0) * 1000
            async with AsyncSessionLocal() as db:
                log = MessageLog(
                    msg_id=str(uuid.uuid4())[:16],
                    msg_type=header["msg_type"] if header else "UNKNOWN",
                    direction="recv",
                    source=f"{addr[0]}:{addr[1]}",
                    raw_data=raw.hex(),
                    parsed_data=json.dumps(parsed_data, default=str) if parsed_data else None,
                    status=status,
                    error_msg=error_msg,
                    process_time=round(elapsed_ms, 2),
                    received_at=datetime.now(),
                )
                db.add(log)
                await db.commit()
        except Exception as e:
            logger.error(f"保存报文日志失败: {e}")

    def send(self, data: bytes, addr: Tuple[str, int]):
        """主动向L1发送报文"""
        if self._transport:
            self._transport.sendto(data, addr)
        else:
            raise RuntimeError("UDP服务未启动")

    def stop(self):
        self._running = False
        if self._transport:
            self._transport.close()


# 全局单例
l1_server = L1UdpServer()
