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

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.EngineV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.ExtremeValueV2016BlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.FuelCellV2016BlockParser;
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.DynamicTest;
import org.junit.jupiter.api.TestFactory;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

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

/**
 * 黄金样本集回放测试：遍历 {@code src/test/resources/samples/gb32960/*.hex}，
 * 逐帧解码并断言命令类型来自 {@link com.lingniu.ingest.protocol.gb32960.model.CommandType} 有效值。
 *
 * <p>样本文件目前为空，该测试会优雅地跳过。加入样本后会自动变为 N 条动态用例。
 */
class Gb32960DecoderGoldenTest {

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

    @TestFactory
    Collection<DynamicTest> replaySamples() throws URISyntaxException, IOException {
        var url = getClass().getClassLoader().getResource("samples/gb32960");
        if (url == null) return List.of();
        Path dir = Paths.get(url.toURI());
        try (Stream<Path> s = Files.list(dir)) {
            return s.filter(p -> p.toString().endsWith(".hex"))
                    .sorted()
                    .map(this::toTest)
                    .collect(Collectors.toList());
        }
    }

    private DynamicTest toTest(Path sample) {
        return DynamicTest.dynamicTest(sample.getFileName().toString(), () -> {
            byte[] frame = readHex(sample);
            Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame));
            assertThat(msg.header().vin()).hasSize(17);
            assertThat(msg.header().command()).isNotNull();
            // 749 字节真实生产帧（realtime_002/003/010/200）：peer 在 V2016 帧里下发了
            // 0x30/0x31/0x32 等 typeCode，但这些在 GB/T 32960.3-2016 附录 B 表 B.3 是"预留"区，
            // 没有任何字段定义。因此**期望产生 Raw 兜底块**——这是符合规范的正确行为。
            // 若未来对端切换到 V2025 (2424 起始) 或者迁移到 0x80~0xFE 用户自定义区，可再调整断言。
            String name = sample.getFileName().toString();
            boolean isRealProductionFrame = frame.length == 774;
            if (isRealProductionFrame) {
                assertThat(msg.findBlock(InfoBlock.Raw.class))
                        .as("[%s] 749字节真实生产帧应有 Raw 兜底块（peer 越界使用 0x30+ 预留 typeCode）", name)
                        .isPresent();
            }
            // 总电流必须落在协议规定的 -1000~+1000 A 区间，防回归到 -3000 偏移
            msg.findBlock(InfoBlock.Gb32960V2016.Vehicle.class).ifPresent(v -> {
                if (v.totalCurrentA() != null) {
                    assertThat(v.totalCurrentA())
                            .as("[%s] vehicle.totalCurrentA 越界，疑似偏移常量回归", name)
                            .isBetween(-1000.0, 1000.0);
                }
            });
        });
    }

    private static byte[] readHex(Path path) throws IOException {
        StringBuilder sb = new StringBuilder();
        for (String line : Files.readAllLines(path)) {
            String trimmed = line.trim();
            if (trimmed.isEmpty() || trimmed.startsWith("#")) continue;
            sb.append(trimmed);
        }
        String hex = sb.toString();
        int len = hex.length() / 2;
        byte[] out = new byte[len];
        for (int i = 0; i < len; i++) {
            out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
        }
        return out;
    }
}
