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

import com.lingniu.ingest.codec.BccChecksum;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.AlarmV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.DriveMotorV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.TemperatureV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VoltageV2016BlockParser;
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;

/**
 * 验证新增的变长信息体 Parser（0x02 驱动电机 / 0x07 报警 / 0x08 电压 / 0x09 温度）
 * 与主解码器协同工作。
 */
class Gb32960FullBlocksTest {

    private final Gb32960MessageDecoder decoder = new Gb32960MessageDecoder(
            new Gb32960BodyParser(new InfoBlockParserRegistry(List.of(
                    new VehicleV2016BlockParser(),
                    new PositionV2016BlockParser(),
                    new DriveMotorV2016BlockParser(),
                    new AlarmV2016BlockParser(),
                    new VoltageV2016BlockParser(),
                    new TemperatureV2016BlockParser()))));

    @Test
    void parsesDriveMotorBlock() {
        byte[] frame = buildFrame(os -> {
            os.write(0x02); // drive motor
            os.write(1);    // 1 motor
            os.write(1);    // serial
            os.write(0x01); // state
            os.write(100);  // controllerTemp = 60
            writeU16(os, 23_000); // rpm = 3000
            writeU16(os, 21_000); // torque raw, actual = 100 Nm
            os.write(90);         // motorTemp = 50
            writeU16(os, 5400);   // voltage 540.0V
            writeU16(os, 11000);  // current raw → 100 A
        });
        Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame));
        InfoBlock.Gb32960V2016.DriveMotor dm = msg.findBlock(InfoBlock.Gb32960V2016.DriveMotor.class).orElseThrow();
        assertThat(dm.motors()).hasSize(1);
        var m = dm.motors().get(0);
        assertThat(m.rpm()).isEqualTo(3000);
        assertThat(m.torqueNm()).isEqualTo(100.0);
        assertThat(m.controllerInputVoltageV()).isEqualTo(540.0);
    }

    @Test
    void parsesAlarmBlock() {
        byte[] frame = buildFrame(os -> {
            os.write(0x07);            // alarm
            os.write(2);               // max level
            writeU32(os, 0x0000_0003L);// general flag
            os.write(1); writeU32(os, 0xDEAD_BEEFL); // battery faults 1
            os.write(0);                              // motor faults 0
            os.write(0);                              // engine faults 0
            os.write(1); writeU32(os, 0xCAFE_BABEL);  // other faults 1
        });
        Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame));
        InfoBlock.Gb32960V2016.Alarm a = msg.findBlock(InfoBlock.Gb32960V2016.Alarm.class).orElseThrow();
        assertThat(a.maxLevel()).isEqualTo(2);
        assertThat(a.batteryFaults()).hasSize(1);
        assertThat(a.otherFaults()).hasSize(1);
    }

    @Test
    void parsesVoltageBlock() {
        byte[] frame = buildFrame(os -> {
            os.write(0x08);        // voltage
            os.write(1);           // 1 subsystem
            os.write(1);           // battery index
            writeU16(os, 3800);    // 380.0 V
            writeU16(os, 11_000);  // current raw
            writeU16(os, 96);      // total cells
            writeU16(os, 1);       // start
            os.write(3);           // frame cells
            writeU16(os, 3500);    // 3.5V
            writeU16(os, 3600);    // 3.6V
            writeU16(os, 3300);    // 3.3V
        });
        Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame));
        InfoBlock.Gb32960V2016.Voltage v = msg.findBlock(InfoBlock.Gb32960V2016.Voltage.class).orElseThrow();
        assertThat(v.subSystemCount()).isEqualTo(1);
        assertThat(v.maxCellVoltageV()).isEqualTo(3.6, org.assertj.core.data.Offset.offset(0.001));
        assertThat(v.minCellVoltageV()).isEqualTo(3.3, org.assertj.core.data.Offset.offset(0.001));
    }

    @Test
    void parsesTemperatureBlock() {
        byte[] frame = buildFrame(os -> {
            os.write(0x09);    // temperature
            os.write(1);       // subsystems
            os.write(1);       // battery index
            writeU16(os, 4);   // probes
            os.write(60);      // actual 20
            os.write(65);      // actual 25
            os.write(70);      // actual 30
            os.write(55);      // actual 15
        });
        Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame));
        InfoBlock.Gb32960V2016.Temperature t = msg.findBlock(InfoBlock.Gb32960V2016.Temperature.class).orElseThrow();
        assertThat(t.maxTempC()).isEqualTo(30);
        assertThat(t.minTempC()).isEqualTo(15);
    }

    // ===== frame builder helpers =====

    private static byte[] buildFrame(java.util.function.Consumer<ByteArrayOutputStream> bodyWriter) {
        ByteArrayOutputStream body = new ByteArrayOutputStream();
        // 时间戳
        body.write(24); body.write(1); body.write(2); body.write(3); body.write(4); body.write(5);
        bodyWriter.accept(body);

        byte[] bodyBytes = body.toByteArray();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write(0x23); out.write(0x23);
        out.write(0x02); // cmd: realtime
        out.write(0xFE); // response flag
        byte[] vin = "LTEST000000000010".getBytes(StandardCharsets.US_ASCII);
        out.write(vin, 0, 17);
        out.write(0x01); // not encrypted
        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));
    }
}
