/*
 * 文 件 名:  MockClientDecoder
 * 版    权:  华设设计集团股份有限公司
 * 描    述:  Mock客户端协议解码器
 * 修 改 人:  lvwenbin
 * 修改时间:  2025/01/07
 * 修改内容:  修复解码时索引越界问题，添加远程停止充电(0x36)支持
 */
package com.ruoyi.ems.charging.mock.handler;

import com.ruoyi.ems.charging.model.BaseFrame;
import com.ruoyi.ems.charging.model.resp.HeartbeatRespFrame;
import com.ruoyi.ems.charging.model.resp.LoginRespFrame;
import com.ruoyi.ems.charging.model.resp.ReadRealtimeDataFrame;
import com.ruoyi.ems.charging.model.resp.RemoteStopFrame;
import com.ruoyi.ems.charging.model.resp.WorkParamSetFrame;
import com.ruoyi.ems.charging.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * Mock客户端协议解码器
 * 负责解析服务端下发的协议帧
 * 服务端下发的帧类型码为偶数
 *
 * @author lvwenbin
 * @version [版本号, 2025/01/07]
 */
public class MockClientDecoder extends ByteToMessageDecoder {

    private static final Logger log = LoggerFactory.getLogger(MockClientDecoder.class);

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 最小帧长度: 起始标志(1) + 数据长度(1) + 最小数据域(4) + CRC(2) = 8
        while (in.readableBytes() >= 8) {
            in.markReaderIndex();

            // 查找起始标志
            byte startFlag = in.readByte();
            if (startFlag != ProtocolConstants.START_FLAG) {
                // 不是起始标志,继续查找
                continue;
            }

            // 读取数据长度
            int dataLength = in.readByte() & 0xFF;

            // 检查数据完整性: 需要 dataLength + 2(CRC) 字节
            if (in.readableBytes() < dataLength + 2) {
                // 数据不完整,等待更多数据
                in.resetReaderIndex();
                return;
            }

            // 记录数据域起始位置（用于CRC计算和帧重构）
            int dataStartIndex = in.readerIndex();

            // 读取序列号、加密标志、帧类型
            int sequenceNo = in.readShortLE() & 0xFFFF;
            byte encryptFlag = in.readByte();
            byte frameType = in.readByte();

            // 计算消息体长度 = 数据域长度 - 序列号(2) - 加密标志(1) - 帧类型(1)
            int bodyLength = dataLength - 4;

            // 跳过消息体
            if (bodyLength > 0) {
                in.skipBytes(bodyLength);
            }

            // 读取CRC
            int receivedCrc = in.readShortLE() & 0xFFFF;

            // 计算CRC（对数据域进行校验）
            byte[] crcData = new byte[dataLength];
            in.readerIndex(dataStartIndex);
            in.readBytes(crcData);
            int calculatedCrc = calculateCRC16(crcData);

            // 跳过CRC字节
            in.skipBytes(2);

            if (calculatedCrc != receivedCrc) {
                log.warn("CRC校验失败, 计算值: 0x{}, 接收值: 0x{}, 帧类型: 0x{}",
                    String.format("%04X", calculatedCrc),
                    String.format("%04X", receivedCrc),
                    String.format("%02X", frameType & 0xFF));
                continue;
            }

            // 根据帧类型创建帧对象
            BaseFrame frame = createFrame(frameType);
            if (frame == null) {
                log.debug("未知的帧类型: 0x{}", String.format("%02X", frameType & 0xFF));
                continue;
            }

            // 构建完整帧数据进行解码
            // 完整帧 = 起始标志(1) + 数据长度(1) + 数据域(dataLength) + CRC(2)
            int totalFrameLength = 2 + dataLength + 2;
            byte[] fullFrame = new byte[totalFrameLength];

            // 回到帧起始位置（起始标志位置）
            in.readerIndex(dataStartIndex - 2);
            in.readBytes(fullFrame);

            if (frame.decode(fullFrame)) {
                out.add(frame);
                if (log.isDebugEnabled()) {
                    log.debug("解码成功: {} [{}字节]", frame, fullFrame.length);
                }
            } else {
                log.warn("帧解码失败: type=0x{}", String.format("%02X", frameType & 0xFF));
            }
        }
    }

    /**
     * 计算CRC16校验值 (Modbus CRC16)
     */
    private int calculateCRC16(byte[] data) {
        int crc = 0xFFFF;
        for (byte b : data) {
            crc ^= (b & 0xFF);
            for (int i = 0; i < 8; i++) {
                if ((crc & 0x0001) != 0) {
                    crc = (crc >> 1) ^ 0xA001;
                } else {
                    crc = crc >> 1;
                }
            }
        }
        return crc & 0xFFFF;
    }

    /**
     * 根据帧类型创建对应的帧对象
     * 这里只处理服务端下发的帧类型(偶数)
     */
    private BaseFrame createFrame(byte frameType) {
        switch (frameType) {
            // 服务端下发的帧(偶数)
            case ProtocolConstants.FRAME_TYPE_LOGIN_RESP:           // 0x02
                return new LoginRespFrame();
            case ProtocolConstants.FRAME_TYPE_HEARTBEAT_RESP:       // 0x04
                return new HeartbeatRespFrame();
            case ProtocolConstants.FRAME_TYPE_READ_REALTIME_DATA:   // 0x12
                return new ReadRealtimeDataFrame();
            case ProtocolConstants.FRAME_TYPE_REMOTE_STOP:          // 0x36 远程停止充电
                return new RemoteStopFrame();
            case ProtocolConstants.FRAME_TYPE_WORK_PARAM_SET:       // 0x52
                return new WorkParamSetFrame();
            default:
                // 使用通用帧处理
                log.debug("使用通用帧处理, 帧类型: 0x{}", String.format("%02X", frameType & 0xFF));
                return new GenericFrame(frameType);
        }
    }

    /**
     * 通用帧 - 用于处理未实现的帧类型
     */
    private static class GenericFrame extends BaseFrame {
        private final byte type;
        private byte[] bodyData;

        public GenericFrame(byte type) {
            this.type = type;
        }

        @Override
        public byte getFrameType() {
            return type;
        }

        @Override
        protected void encodeBody(ByteBuf buf) {
            if (bodyData != null) {
                buf.writeBytes(bodyData);
            }
        }

        @Override
        protected void decodeBody(ByteBuf buf) {
            if (buf.readableBytes() > 0) {
                bodyData = new byte[buf.readableBytes()];
                buf.readBytes(bodyData);
            }
        }

        @Override
        public String toString() {
            return String.format("GenericFrame[type=0x%02X, bodyLen=%d]",
                type & 0xFF, bodyData != null ? bodyData.length : 0);
        }
    }
}