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

import com.lingniu.ingest.codec.BccChecksum;
import com.lingniu.ingest.codec.BcdCodec;
import com.lingniu.ingest.protocol.jt808.model.Jt808Header;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * JT/T 808 下行帧编码器：把 header + body 字节拼装 → 加 XOR 校验 → 转义 → 外包 0x7e。
 *
 * <p>支持 2013 与 2019 版基础头；短命令可直接编码，长命令通过 {@link #encodeSubpackages}
 * 拆分为 JT/T 808 分包帧。
 */
public final class Jt808FrameEncoder {

    private static final int MAX_BODY_LENGTH = 0x03FF;

    private Jt808FrameEncoder() {}

    public static byte[] encode(int messageId, String phone, int serialNo, byte[] body) {
        return encode(messageId, phone, serialNo, body, Jt808Header.ProtocolVersion.V2013);
    }

    public static byte[] encode(int messageId, String phone, int serialNo, byte[] body,
                                 Jt808Header.ProtocolVersion version) {
        byte[] raw = buildRaw(messageId, phone, serialNo, body, version, 0, 0);
        return wrap(raw);
    }

    public static List<byte[]> encodeSubpackages(int messageId, String phone, int serialNo, byte[] body) {
        return encodeSubpackages(messageId, phone, serialNo, body, Jt808Header.ProtocolVersion.V2013);
    }

    public static List<byte[]> encodeSubpackages(int messageId,
                                                 String phone,
                                                 int serialNo,
                                                 byte[] body,
                                                 Jt808Header.ProtocolVersion version) {
        if (body == null) {
            body = new byte[0];
        }
        if (body.length <= MAX_BODY_LENGTH) {
            return List.of(encode(messageId, phone, serialNo, body, version));
        }
        int totalPackets = (body.length + MAX_BODY_LENGTH - 1) / MAX_BODY_LENGTH;
        if (totalPackets > 0xFFFF) {
            throw new IllegalArgumentException("jt808 subpackage count too large: " + totalPackets);
        }
        List<byte[]> frames = new ArrayList<>(totalPackets);
        for (int i = 0; i < totalPackets; i++) {
            int offset = i * MAX_BODY_LENGTH;
            int len = Math.min(MAX_BODY_LENGTH, body.length - offset);
            byte[] chunk = new byte[len];
            System.arraycopy(body, offset, chunk, 0, len);
            frames.add(wrap(buildRaw(messageId, phone, serialNo, chunk, version, totalPackets, i + 1)));
        }
        return List.copyOf(frames);
    }

    private static byte[] wrap(byte[] raw) {
        byte bcc = BccChecksum.compute(raw, 0, raw.length);

        ByteArrayOutputStream almost = new ByteArrayOutputStream(raw.length + 1);
        almost.write(raw, 0, raw.length);
        almost.write(bcc & 0xFF);

        byte[] escaped = Jt808Escape.escape(almost.toByteArray());
        byte[] out = new byte[escaped.length + 2];
        out[0] = 0x7e;
        System.arraycopy(escaped, 0, out, 1, escaped.length);
        out[out.length - 1] = 0x7e;
        return out;
    }

    private static byte[] buildRaw(int messageId, String phone, int serialNo, byte[] body,
                                    Jt808Header.ProtocolVersion version, int totalPackets, int packetSeq) {
        if (body == null) {
            body = new byte[0];
        }
        if (body.length > MAX_BODY_LENGTH) {
            throw new IllegalArgumentException("jt808 body too large without subpackage: "
                    + body.length + " > " + MAX_BODY_LENGTH);
        }
        boolean subpacket = totalPackets > 0;
        boolean v2019 = version == Jt808Header.ProtocolVersion.V2019;
        int phoneLen = v2019 ? 10 : 6;
        int len = 2 /* msgId */ + 2 /* attrs */ + (v2019 ? 1 : 0)
                + phoneLen + 2 /* serial */ + (subpacket ? 4 : 0) + body.length;
        ByteArrayOutputStream os = new ByteArrayOutputStream(len);

        // msgId
        os.write((messageId >> 8) & 0xFF);
        os.write(messageId & 0xFF);

        // attrs: 2019 版 bit14 置 1
        int attrs = body.length & 0x03FF;
        if (subpacket) attrs |= 0x2000;
        if (v2019) attrs |= 0x4000;
        os.write((attrs >> 8) & 0xFF);
        os.write(attrs & 0xFF);

        // 2019 版多一个 protocolVersion 字节（值 1）
        if (v2019) os.write(0x01);

        // phone BCD
        String padded = padLeft(phone, phoneLen * 2);
        byte[] bcd = BcdCodec.encode(padded);
        os.write(bcd, 0, phoneLen);

        // serial
        os.write((serialNo >> 8) & 0xFF);
        os.write(serialNo & 0xFF);

        if (subpacket) {
            os.write((totalPackets >> 8) & 0xFF);
            os.write(totalPackets & 0xFF);
            os.write((packetSeq >> 8) & 0xFF);
            os.write(packetSeq & 0xFF);
        }

        // body
        os.write(body, 0, body.length);
        return os.toByteArray();
    }

    private static String padLeft(String s, int len) {
        if (s == null) s = "";
        if (s.length() >= len) return s.substring(s.length() - len);
        StringBuilder sb = new StringBuilder(len);
        for (int i = s.length(); i < len; i++) sb.append('0');
        sb.append(s);
        return sb.toString();
    }
}
