package com.lingniu.ingest.protocol.gb32960.codec;

import com.lingniu.ingest.api.spi.DecodeException;
import com.lingniu.ingest.api.spi.FrameDecoder;
import com.lingniu.ingest.codec.BccChecksum;
import com.lingniu.ingest.protocol.gb32960.model.CommandBody;
import com.lingniu.ingest.protocol.gb32960.model.CommandType;
import com.lingniu.ingest.protocol.gb32960.model.EncryptType;
import com.lingniu.ingest.protocol.gb32960.model.Gb32960Header;
import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message;
import com.lingniu.ingest.protocol.gb32960.model.InfoBlock;
import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion;
import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.List;

/**
 * 完整帧 → {@link Gb32960Message} 的解码器。
 *
 * <p>支持 GB/T 32960.3 两个版本：
 * <ul>
 *   <li>{@code 0x23 0x23 (##)} → 2016 版（{@link ProtocolVersion#V2016}）
 *   <li>{@code 0x24 0x24 ($$)} → 2025 版（{@link ProtocolVersion#V2025}）
 * </ul>
 *
 * <p>完整数据包结构（表 2 / 表 B.1）：
 * <pre>
 *   offset 0:  起始符 (2B)
 *   offset 2:  命令标识 (1B)
 *   offset 3:  应答标志 (1B)
 *   offset 4:  唯一识别码 VIN (17B ASCII)
 *   offset 21: 数据单元加密方式 (1B)
 *   offset 22: 数据单元长度 (2B WORD 大端)
 *   offset 24: 数据单元 (dataLength 字节)
 *   last byte: 校验码 (1B BCC)
 * </pre>
 *
 * <p>BCC 校验范围：从命令单元的第一个字节开始（offset 2），同后一字节异或，
 * 直到校验码前一字节为止。注意：当数据单元存在加密时，应先加密后校验，解析时先校验后解密。
 *
 * <p>上游 Netty 的 frame handler 已经保证 {@code buffer} 内是一条完整帧。
 */
public final class Gb32960MessageDecoder implements FrameDecoder<Gb32960Message> {

    /** 最小帧长：2(起始符)+1(cmd)+1(rsp)+17(vin)+1(enc)+2(len)+0(body)+1(bcc)。 */
    private static final int MIN_FRAME = 25;
    private static final ZoneId ZONE_GMT8 = ZoneId.of("Asia/Shanghai");

    private final Gb32960BodyParser bodyParser;
    private final Gb32960CommandParser commandParser;

    public Gb32960MessageDecoder(Gb32960BodyParser bodyParser) {
        this(bodyParser, new Gb32960CommandParser());
    }

    public Gb32960MessageDecoder(Gb32960BodyParser bodyParser, Gb32960CommandParser commandParser) {
        this.bodyParser = bodyParser;
        this.commandParser = commandParser;
    }

    @Override
    public Gb32960Message decode(ByteBuffer buffer) {
        return decode(buffer, null);
    }

    /**
     * 带 platformAccount 提示的解码。Handler 在已经完成 0x05 PLATFORM_LOGIN 鉴权后通过
     * channel attribute 取到账号名再传进来；以便 BodyParser 内部的 vendor profile selector
     * 路由。其他命令传 {@code null} 即可。
     */
    public Gb32960Message decode(ByteBuffer buffer, String platformAccount) {
        int frameLen = buffer.remaining();
        if (frameLen < MIN_FRAME) {
            throw new DecodeException("frame too short: " + frameLen);
        }

        byte[] frame = new byte[frameLen];
        buffer.get(frame);

        ProtocolVersion version = detectVersion(frame);

        // BCC 校验：从 cmd(offset 2) 开始到 BCC 前一字节
        byte expectedBcc = frame[frame.length - 1];
        if (!BccChecksum.verify(frame, 2, frame.length - 3, expectedBcc)) {
            throw new DecodeException("BCC mismatch");
        }

        int commandCode = frame[2] & 0xFF;
        int responseCode = frame[3] & 0xFF;
        String vin = new String(frame, 4, 17, StandardCharsets.US_ASCII).trim();
        int encryptCode = frame[21] & 0xFF;
        int dataLength = ((frame[22] & 0xFF) << 8) | (frame[23] & 0xFF);
        int actualDataLength = frame.length - 25;

        int bodyStart = 24;
        if (bodyStart + dataLength + 1 != frame.length
                && !isExtendedPlatformLogin(commandCode, dataLength, actualDataLength)) {
            throw new DecodeException("declared body length " + dataLength
                    + " does not match frame remainder " + (frame.length - bodyStart - 1));
        }

        CommandType cmdType = CommandType.of(commandCode);
        ResponseFlag rspFlag = ResponseFlag.of(responseCode);
        EncryptType encType = EncryptType.of(encryptCode);

        // 实时 / 补发上报：解析信息体（和 2025 版尾部签名）
        if (cmdType == CommandType.REALTIME_REPORT || cmdType == CommandType.RESEND_REPORT) {
            if (dataLength < 6) throw new DecodeException("report body missing timestamp");
            Instant eventTime = parseTimestamp(frame, bodyStart);
            ByteBuffer body = ByteBuffer.wrap(frame, bodyStart + 6, dataLength - 6);
            Gb32960ParserContext ctx = new Gb32960ParserContext(version, vin, platformAccount);
            BodyParseResult result = bodyParser.parse(ctx, body);
            Gb32960Header header = new Gb32960Header(
                    version, cmdType, rspFlag, vin, encType, dataLength, eventTime);
            return new Gb32960Message(header, result.blocks(), null, result.signature());
        }

        // 其他命令：解析 command body
        // 车辆/平台登入登出 body 首 6B 为采集时间（表 5），预解析并写入 header.eventTime，
        // 以便 handler 构造应答帧时按 §6.3.2 保留原报文时间。
        Instant eventTime = null;
        if (hasLeadingTimestamp(cmdType) && dataLength >= 6) {
            eventTime = parseTimestamp(frame, bodyStart);
        }
        Gb32960Header headerStub = new Gb32960Header(
                version, cmdType, rspFlag, vin, encType, dataLength, eventTime);
        int commandBodyLength = isExtendedPlatformLogin(commandCode, dataLength, actualDataLength)
                ? actualDataLength
                : dataLength;
        ByteBuffer body = ByteBuffer.wrap(frame, bodyStart, commandBodyLength);
        CommandBody cmdBody = commandParser.parse(cmdType, body);
        return new Gb32960Message(headerStub, Collections.emptyList(), cmdBody, null);
    }

    private static boolean isExtendedPlatformLogin(int commandCode, int declaredDataLength, int actualDataLength) {
        // 与 FrameDecoder 的兼容规则保持一致：部分平台登录帧声明长度 41B，但真实账号/密码体为 53B。
        return commandCode == 0x05 && declaredDataLength == 41 && actualDataLength == 53;
    }

    private static ProtocolVersion detectVersion(byte[] frame) {
        if (frame[0] == 0x23 && frame[1] == 0x23) return ProtocolVersion.V2016;
        if (frame[0] == 0x24 && frame[1] == 0x24) return ProtocolVersion.V2025;
        throw new DecodeException("unknown start symbol: "
                + String.format("%02x %02x", frame[0] & 0xFF, frame[1] & 0xFF));
    }

    private static boolean hasLeadingTimestamp(CommandType cmd) {
        return cmd == CommandType.VEHICLE_LOGIN
                || cmd == CommandType.VEHICLE_LOGOUT
                || cmd == CommandType.PLATFORM_LOGIN
                || cmd == CommandType.PLATFORM_LOGOUT;
    }

    /** 时间定义（表 5）：YY MM DD HH MM SS，各 1 字节，GMT+8。 */
    private static Instant parseTimestamp(byte[] frame, int offset) {
        int year = 2000 + (frame[offset] & 0xFF);
        int month = frame[offset + 1] & 0xFF;
        int day = frame[offset + 2] & 0xFF;
        int hour = frame[offset + 3] & 0xFF;
        int minute = frame[offset + 4] & 0xFF;
        int second = frame[offset + 5] & 0xFF;
        try {
            return LocalDateTime.of(year, month, day, hour, minute, second)
                    .atZone(ZONE_GMT8)
                    .toInstant();
        } catch (Exception e) {
            throw new DecodeException("invalid timestamp " + year + "-" + month + "-" + day
                    + " " + hour + ":" + minute + ":" + second, e);
        }
    }

    /** 实时上报 body 解析结果：信息体列表 + 可选签名字节。 */
    public record BodyParseResult(List<InfoBlock> blocks, byte[] signature) {}
}
