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

import com.lingniu.ingest.codec.BccChecksum;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.model.CommandBody;
import com.lingniu.ingest.protocol.gb32960.model.CommandType;
import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message;
import com.lingniu.ingest.protocol.gb32960.model.InfoBlock;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * 构造一条合成 32960 实时上报帧，跑通 Frame 解码 → Body 解析 → InfoBlock 的全链路。
 *
 * <p>样本无需外部文件：本测试既验证解码正确性，也作为 {@code samples/} 黄金样本的期望值参考实现。
 */
public class Gb32960DecoderTest {

    @Test
    void decodesSyntheticRealtimeReport() {
        byte[] frame = buildRealtimeFrame("LTEST000000000001");

        InfoBlockParserRegistry registry = new InfoBlockParserRegistry(
                List.of(new VehicleV2016BlockParser(), new PositionV2016BlockParser()));
        Gb32960BodyParser bodyParser = new Gb32960BodyParser(registry);
        Gb32960MessageDecoder decoder = new Gb32960MessageDecoder(bodyParser);

        Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame));

        assertThat(msg.header().command()).isEqualTo(CommandType.REALTIME_REPORT);
        assertThat(msg.header().vin()).isEqualTo("LTEST000000000001");
        assertThat(msg.header().eventTime()).isNotNull();

        InfoBlock.Gb32960V2016.Vehicle v = msg.findBlock(InfoBlock.Gb32960V2016.Vehicle.class).orElseThrow();
        assertThat(v.socPercent()).isEqualTo(70);
        assertThat(v.speedKmh()).isEqualTo(52.3, org.assertj.core.data.Offset.offset(0.01));
        assertThat(v.gearRaw()).isEqualTo(0x0F);

        InfoBlock.Gb32960V2016.Position p = msg.findBlock(InfoBlock.Gb32960V2016.Position.class).orElseThrow();
        assertThat(p.longitude()).isEqualTo(116.397128, org.assertj.core.data.Offset.offset(0.000001));
        assertThat(p.latitude()).isEqualTo(39.916527, org.assertj.core.data.Offset.offset(0.000001));
    }

    @Test
    void decodesPlatformLoginWithMd5HexPasswordLongerThanDeclaredGbField() {
        byte[] frame = buildPlatformLoginFrameWithExtendedPassword(
                "Hyundai",
                "f2e3445d7cda409fb4f278f6fb890734");

        Gb32960MessageDecoder decoder = new Gb32960MessageDecoder(new Gb32960BodyParser(
                new InfoBlockParserRegistry(List.of())));

        Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame));

        assertThat(msg.header().command()).isEqualTo(CommandType.PLATFORM_LOGIN);
        assertThat(msg.commandBody()).isInstanceOf(CommandBody.PlatformLogin.class);
        CommandBody.PlatformLogin login = (CommandBody.PlatformLogin) msg.commandBody();
        assertThat(login.username()).isEqualTo("Hyundai");
        assertThat(login.password()).isEqualTo("f2e3445d7cda409fb4f278f6fb890734");
    }

    /**
     * 构造一条合法的 32960 实时上报帧：
     * header + 6B 时间戳(2024-01-02 03:04:05) + 0x01 整车 + 0x05 位置 + BCC
     */
    public static byte[] buildRealtimeFrame(String vin) {
        ByteArrayOutputStream body = new ByteArrayOutputStream();
        // 时间戳
        body.write(24); body.write(1); body.write(2); body.write(3); body.write(4); body.write(5);

        // 0x01 整车 20 字节
        body.write(0x01);
        body.write(0x01); // vehicle state = 启动
        body.write(0x03); // charging = 未充电
        body.write(0x01); // 纯电
        writeU16(body, 523); // 车速 52.3 km/h
        writeU32(body, 1234567); // 里程 123456.7 km
        writeU16(body, 6000); // 600.0 V
        writeU16(body, 1100); // 110.0 A (110 - (1000-1000) = 10)  实际计算：1100*0.1 - 1000 = -890；此处只为结构测试
        body.write(70); // SOC
        body.write(0x01);
        body.write(0x0F);
        writeU16(body, 500);
        body.write(30);
        body.write(0);

        // 0x05 位置 9 字节
        body.write(0x05);
        body.write(0x00); // 有效 + 北纬 + 东经
        writeU32(body, 116_397_128L);
        writeU32(body, 39_916_527L);

        byte[] bodyBytes = body.toByteArray();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write(0x23); out.write(0x23);
        out.write(0x02); // 实时上报
        out.write(0xFE); // 应答 / 命令
        byte[] vinBytes = vin.getBytes(StandardCharsets.US_ASCII);
        out.write(vinBytes, 0, 17);
        out.write(0x01); // 不加密
        writeU16(out, bodyBytes.length);
        out.write(bodyBytes, 0, bodyBytes.length);

        byte[] almost = out.toByteArray();
        byte bcc = BccChecksum.compute(almost, 2, almost.length - 2);
        out.write(bcc & 0xFF);
        return out.toByteArray();
    }

    private static void writeU16(ByteArrayOutputStream os, int v) {
        os.write((v >> 8) & 0xFF);
        os.write(v & 0xFF);
    }

    private static void writeU32(ByteArrayOutputStream os, long v) {
        os.write((int) ((v >> 24) & 0xFF));
        os.write((int) ((v >> 16) & 0xFF));
        os.write((int) ((v >> 8) & 0xFF));
        os.write((int) (v & 0xFF));
    }

    private static byte[] buildPlatformLoginFrameWithExtendedPassword(String username, String password) {
        ByteArrayOutputStream body = new ByteArrayOutputStream();
        body.write(26); body.write(6); body.write(22); body.write(20); body.write(40); body.write(45);
        writeU16(body, 1);
        writePaddedAscii(body, username, 12);
        writePaddedAscii(body, password, password.length());
        body.write(0x01);

        byte[] bodyBytes = body.toByteArray();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write(0x23); out.write(0x23);
        out.write(0x05);
        out.write(0xFE);
        for (int i = 0; i < 17; i++) out.write(0);
        out.write(0x01);
        writeU16(out, 41);
        out.write(bodyBytes, 0, bodyBytes.length);
        byte[] almost = out.toByteArray();
        out.write(BccChecksum.compute(almost, 2, almost.length - 2) & 0xFF);
        return out.toByteArray();
    }

    private static void writePaddedAscii(ByteArrayOutputStream os, String value, int len) {
        byte[] bytes = value.getBytes(StandardCharsets.US_ASCII);
        int copy = Math.min(bytes.length, len);
        os.write(bytes, 0, copy);
        for (int i = copy; i < len; i++) os.write(0);
    }
}
