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

import com.lingniu.ingest.codec.BccChecksum;
import com.lingniu.ingest.protocol.gb32960.model.CommandType;
import com.lingniu.ingest.protocol.gb32960.model.EncryptType;
import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion;
import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

/**
 * GB/T 32960.3 下行帧编码器。主要用于应答场景：平台收到终端上行命令后发送应答。
 *
 * <p>应答规则（§6.3.2）：服务端发送应答时，变更应答标志、保留报文时间、删除其余报文内容、
 * 重新计算校验位。
 *
 * <p>该类只做协议编码，不写 Channel。是否立即关闭连接由 {@code Gb32960AckService}
 * 根据鉴权结果决定。
 */
public final class Gb32960FrameEncoder {

    private static final ZoneId ZONE_GMT8 = ZoneId.of("Asia/Shanghai");

    private Gb32960FrameEncoder() {}

    /**
     * 构造简易应答帧：复用原命令的 command/vin/encryptType，修改应答标志，
     * 可选携带数据单元（通常只带 6 字节采集时间）。
     *
     * @param version     协议版本（决定起始符）
     * @param command     命令标识（与原命令相同）
     * @param responseFlag 应答标志：{@link ResponseFlag#SUCCESS}/{@link ResponseFlag#VIN_NOT_EXIST} 等
     * @param vin         17 字节 VIN
     * @param collectTime 数据采集时间（保留原帧时间，若无则传 {@code Instant.now()}）
     * @param extraBody   附加数据（可为 null）
     */
    public static byte[] buildResponse(ProtocolVersion version,
                                       CommandType command,
                                       ResponseFlag responseFlag,
                                       String vin,
                                       Instant collectTime,
                                       byte[] extraBody) {
        return buildResponse(version, command, responseFlag, vinPadded(vin), collectTime, extraBody);
    }

    /**
     * 同 {@link #buildResponse(ProtocolVersion, CommandType, ResponseFlag, String, Instant, byte[])}，
     * 但直接接收 17 字节原始 VIN。用于回执时原样回显请求帧的 VIN 字节，避免
     * 平台登入这类 VIN 全 0 的场景被我们用空格重写后导致对端判定 VIN 不匹配。
     */
    public static byte[] buildResponse(ProtocolVersion version,
                                       CommandType command,
                                       ResponseFlag responseFlag,
                                       byte[] vin17,
                                       Instant collectTime,
                                       byte[] extraBody) {
        ByteArrayOutputStream body = new ByteArrayOutputStream();
        // 数据采集时间 6 字节（表 5）
        if (collectTime != null) {
            LocalDateTime ldt = LocalDateTime.ofInstant(collectTime, ZONE_GMT8);
            body.write(ldt.getYear() - 2000);
            body.write(ldt.getMonthValue());
            body.write(ldt.getDayOfMonth());
            body.write(ldt.getHour());
            body.write(ldt.getMinute());
            body.write(ldt.getSecond());
        }
        if (extraBody != null && extraBody.length > 0) {
            body.write(extraBody, 0, extraBody.length);
        }

        byte[] bodyBytes = body.toByteArray();
        int dataLength = bodyBytes.length;

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // 起始符
        if (version == ProtocolVersion.V2025) {
            out.write(0x24); out.write(0x24);
        } else {
            out.write(0x23); out.write(0x23);
        }
        out.write(command.code());
        out.write(responseFlag.code());
        // VIN（17 字节，由调用方给出；平台登入等场景直接回显请求的原始字节）
        byte[] vinBytes = vin17 != null && vin17.length == 17 ? vin17 : vinPadded(null);
        out.write(vinBytes, 0, 17);
        // 加密方式：应答不加密
        out.write(EncryptType.UNENCRYPTED.code());
        // 数据单元长度 WORD 大端
        out.write((dataLength >> 8) & 0xFF);
        out.write(dataLength & 0xFF);
        out.write(bodyBytes, 0, bodyBytes.length);
        // BCC：从 cmd(offset 2) 到数据单元结尾
        byte[] almost = out.toByteArray();
        byte bcc = BccChecksum.compute(almost, 2, almost.length - 2);
        out.write(bcc & 0xFF);
        return out.toByteArray();
    }

    private static byte[] vinPadded(String vin) {
        byte[] out = new byte[17];
        // 填充空格
        java.util.Arrays.fill(out, (byte) ' ');
        if (vin == null) return out;
        byte[] src = vin.getBytes(StandardCharsets.US_ASCII);
        int len = Math.min(src.length, 17);
        System.arraycopy(src, 0, out, 0, len);
        return out;
    }
}
