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

import com.lingniu.ingest.api.spi.DecodeException;
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.InfoBlock;
import com.lingniu.ingest.protocol.gb32960.model.InfoBlockType;
import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion;
import org.junit.jupiter.api.Test;

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

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

/**
 * 验证 {@link Gb32960BodyParser} 单块异常隔离行为。
 *
 * <p>三个隔离场景：
 * <ol>
 *   <li>固定长度块 parser 抛异常 → 失败块兜 Raw（按 fixedLen 截取）+ 后续块继续解析
 *   <li>固定长度块剩余字节不足（截断帧） → 兜 Raw(剩余) + break 循环
 *   <li>变长块 parser 抛异常 → 兜 Raw(从失败块起剩余全部) + break 循环
 * </ol>
 *
 * <p>外加一个严格模式回退测试：{@code lenientBlockFailure=false} 时异常应抛 DecodeException。
 */
class Gb32960BodyParserIsolationTest {

    /** 模拟一个固定长度的 Position parser，声明 fixedLength=9，parse 时主动抛异常。 */
    private static final InfoBlockParser EXPLODING_FIXED_LEN_POSITION = new InfoBlockParser() {
        @Override public ProtocolVersion version() { return ProtocolVersion.V2016; }
        @Override public int typeCode() { return 0x05; }
        @Override public int fixedLength() { return 9; }
        @Override public InfoBlock parse(ByteBuffer buffer) {
            buffer.get();
            buffer.get();
            throw new DecodeException("simulated parser failure in Position");
        }
    };

    /** 模拟一个变长 Alarm parser（fixedLength=-1），消费若干字节后抛。 */
    private static final InfoBlockParser EXPLODING_VAR_LEN_ALARM = new InfoBlockParser() {
        @Override public ProtocolVersion version() { return ProtocolVersion.V2016; }
        @Override public int typeCode() { return 0x07; }
        @Override public int fixedLength() { return -1; }
        @Override public InfoBlock parse(ByteBuffer buffer) {
            buffer.get();
            throw new DecodeException("simulated parser failure in Alarm list-length read");
        }
    };

    @Test
    void fixedLengthBlockFailure_isIsolated_subsequentBlocksStillParsed() {
        InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of(
                new VehicleV2016BlockParser(),
                EXPLODING_FIXED_LEN_POSITION));

        Gb32960BodyParser parser = new Gb32960BodyParser(registry);

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        writeValidVehicle(os);                // 1 + 20 = 21B
        writePositionTypeAnd9ByteBody(os);    // 1 + 9  = 10B（parser 会爆）
        writeValidVehicle(os);                // 1 + 20 = 21B
        ByteBuffer body = ByteBuffer.wrap(os.toByteArray());

        var result = parser.parse(ProtocolVersion.V2016, body);

        assertThat(result.blocks()).hasSize(3);
        assertThat(result.blocks().get(0)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class);
        assertThat(result.blocks().get(1)).isInstanceOfSatisfying(InfoBlock.Raw.class, raw -> {
            assertThat(raw.typeCode()).isEqualTo(0x05);
            assertThat(raw.type()).isEqualTo(InfoBlockType.RAW);
            assertThat(raw.bytes()).hasSize(9);
        });
        assertThat(result.blocks().get(2)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class);
        assertThat(body.hasRemaining()).isFalse();
    }

    @Test
    void truncatedFixedLengthBlock_isWrappedAsRaw_loopTerminates() {
        InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of(
                new VehicleV2016BlockParser(),
                new PositionV2016BlockParser()));

        Gb32960BodyParser parser = new Gb32960BodyParser(registry);

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        writeValidVehicle(os);
        os.write(0x05);
        os.write(0);
        os.write(0);
        os.write(0);
        ByteBuffer body = ByteBuffer.wrap(os.toByteArray());

        var result = parser.parse(ProtocolVersion.V2016, body);

        assertThat(result.blocks()).hasSize(2);
        assertThat(result.blocks().get(0)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class);
        assertThat(result.blocks().get(1)).isInstanceOfSatisfying(InfoBlock.Raw.class, raw -> {
            assertThat(raw.typeCode()).isEqualTo(0x05);
            assertThat(raw.bytes()).hasSize(3);
        });
    }

    @Test
    void variableLengthBlockFailure_swallowsRemainderAsRaw_loopBreaks() {
        InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of(
                new VehicleV2016BlockParser(),
                EXPLODING_VAR_LEN_ALARM));

        Gb32960BodyParser parser = new Gb32960BodyParser(registry);

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        writeValidVehicle(os);
        os.write(0x07);
        for (int i = 0; i < 10; i++) os.write(0xAA);
        writeValidVehicle(os);
        ByteBuffer body = ByteBuffer.wrap(os.toByteArray());

        var result = parser.parse(ProtocolVersion.V2016, body);

        assertThat(result.blocks()).hasSize(2);
        assertThat(result.blocks().get(0)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class);
        assertThat(result.blocks().get(1)).isInstanceOfSatisfying(InfoBlock.Raw.class, raw -> {
            assertThat(raw.typeCode()).isEqualTo(0x07);
            assertThat(raw.bytes()).hasSize(31);
        });
    }

    @Test
    void strictMode_throwsOnAnyBlockFailure() {
        InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of(
                new VehicleV2016BlockParser(),
                EXPLODING_FIXED_LEN_POSITION));

        Gb32960BodyParser parser = new Gb32960BodyParser(registry);
        parser.setLenientBlockFailure(false);

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        writeValidVehicle(os);
        writePositionTypeAnd9ByteBody(os);
        ByteBuffer body = ByteBuffer.wrap(os.toByteArray());

        assertThatThrownBy(() -> parser.parse(ProtocolVersion.V2016, body))
                .isInstanceOf(DecodeException.class);
    }

    private static void writeValidVehicle(ByteArrayOutputStream os) {
        os.write(0x01);
        os.write(0x01);
        os.write(0x01);
        os.write(0x01);
        os.write(0); os.write(0);
        os.write(0); os.write(0); os.write(0); os.write(0);
        os.write(0); os.write(0);
        os.write(0); os.write(0);
        os.write(50);
        os.write(0x01);
        os.write(0);
        os.write(0); os.write(0);
        os.write(0);
        os.write(0);
    }

    private static void writePositionTypeAnd9ByteBody(ByteArrayOutputStream os) {
        os.write(0x05);
        for (int i = 0; i < 9; i++) os.write(0);
    }
}
