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.codec.parser.vendor.guangdong.GdFcAirConditionerBlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcAuxiliaryBlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcDcDcBlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcDemoExtensionBlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcStackBlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcVehicleInfoBlockParser;
import com.lingniu.ingest.protocol.gb32960.codec.profile.Gb32960ProfileRegistry;
import com.lingniu.ingest.protocol.gb32960.codec.profile.RuleBasedVendorExtensionSelector;
import com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionCatalog;
import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties;
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960ChannelHandler;
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 java.util.Set;

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

/**
 * 端到端集成测试：构造一帧带广东燃料电池规范扩展块的 V2016 帧，验证
 * vendor profile selector 路由 + GuangdongFc 系列 parser 全链路解码。
 *
 * <p>覆盖：
 * <ul>
 *   <li>9 个 GB/T 32960 标准块（0x01~0x09）原生解析
 *   <li>0x30 GuangdongFc.Stack（含 1 个电堆、4 个单体电压样本）
 *   <li>0x32 GuangdongFc.DcDc
 *   <li>0x33 GuangdongFc.AirConditioner
 *   <li>0x34 GuangdongFc.VehicleInfo
 *   <li>0x80 GuangdongFc.DemoExtension
 *   <li>VendorExtensionSelector 按 platformAccount=lingniu 命中
 * </ul>
 *
 * <p>0x31 Auxiliary 没有放进帧里（变长且字段相对琐碎），单独由 unit test 覆盖。
 */
class GuangdongFcEndToEndTest {

    private final Gb32960MessageDecoder decoder = buildDecoder();

    @Test
    void parsesRealtimeFrameWithGuangdongFcExtensions() {
        byte[] frame = buildFrame();
        // 用 platformAccount=lingniu 触发 vendor profile
        Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame), "lingniu");

        // ---- 标准块 ----
        var v = msg.findBlock(InfoBlock.Gb32960V2016.Vehicle.class).orElseThrow();
        assertThat(v.socPercent()).isEqualTo(85);

        var pos = msg.findBlock(InfoBlock.Gb32960V2016.Position.class).orElseThrow();
        assertThat(pos.longitude()).isCloseTo(120.848158, within(1e-6));

        // ---- vendor 块：0x30 Stack ----
        var stack = msg.findBlock(InfoBlock.GuangdongFc.Stack.class).orElseThrow();
        assertThat(stack.stackCount()).isEqualTo(1);
        var s0 = stack.stacks().get(0);
        assertThat(s0.engineWorkState()).isEqualTo(1);
        assertThat(s0.stackWaterOutletTempC()).isEqualTo(50);   // 90-40
        assertThat(s0.maxCellVoltageV()).isCloseTo(0.745, within(1e-6));   // 745 mV
        assertThat(s0.minCellVoltageV()).isCloseTo(0.730, within(1e-6));
        assertThat(s0.avgCellVoltageV()).isCloseTo(0.738, within(1e-6));
        assertThat(s0.cellCount()).isEqualTo(400);
        assertThat(s0.frameCellCount()).isEqualTo(4);
        assertThat(s0.frameCellVoltagesV()).hasSize(4)
                .allSatisfy(c -> assertThat(c).isBetween(0.7, 0.8));

        // ---- vendor 块：0x32 DcDc ----
        var dcdc = msg.findBlock(InfoBlock.GuangdongFc.DcDc.class).orElseThrow();
        assertThat(dcdc.inputVoltageV()).isCloseTo(300.0, within(1e-6));
        assertThat(dcdc.outputCurrentA()).isCloseTo(110.0, within(1e-6));

        // ---- vendor 块：0x33 AirConditioner ----
        var ac = msg.findBlock(InfoBlock.GuangdongFc.AirConditioner.class).orElseThrow();
        assertThat(ac.status()).isEqualTo(1);
        assertThat(ac.powerKw()).isEqualTo(15);

        // ---- vendor 块：0x34 VehicleInfo ----
        var info = msg.findBlock(InfoBlock.GuangdongFc.VehicleInfo.class).orElseThrow();
        assertThat(info.collisionAlarm()).isEqualTo(0);
        assertThat(info.ambientTempC()).isEqualTo(30);
        assertThat(info.hydrogenMassKg()).isCloseTo(8.0, within(1e-6));

        // ---- vendor 块：0x80 DemoExtension ----
        var demo = msg.findBlock(InfoBlock.GuangdongFc.DemoExtension.class).orElseThrow();
        assertThat(demo.stackTempC()).isEqualTo(50);
        assertThat(demo.airCompressorVoltageV()).isCloseTo(372.0, within(1e-6));

        // 不应有 Raw 兜底：所有块都被结构化解析
        assertThat(msg.findBlock(InfoBlock.Raw.class)).isEmpty();
    }

    @Test
    void unmatchedAccountFallsBackToDefaultAndProducesRawForVendorBlocks() {
        byte[] frame = buildFrame();
        // 不传 account → selector 返回 null → default profile（无 vendor parser）
        Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame), null);

        // 标准块仍能解析
        assertThat(msg.findBlock(InfoBlock.Gb32960V2016.Vehicle.class)).isPresent();
        // 但 0x30 应触发 unknown → Raw 兜底
        assertThat(msg.findBlock(InfoBlock.Raw.class)).isPresent();
        assertThat(msg.findBlock(InfoBlock.GuangdongFc.Stack.class)).isEmpty();
    }

    // ====================================================================
    // 帧构造：标准块 0x01/0x05/0x07/0x08/0x09 + vendor 块 0x30/0x32/0x33/0x34/0x80
    // 不带 0x02/0x04/0x06/0x31，纯粹为了控制帧长和测试焦点。
    // ====================================================================
    private static byte[] buildFrame() {
        ByteArrayOutputStream body = new ByteArrayOutputStream();
        // 时间戳 6B
        body.write(24); body.write(4); body.write(15); body.write(11); body.write(0); body.write(0);

        // 0x01 整车数据 20B
        body.write(0x01);
        body.write(0x01); // vehicleState 启动
        body.write(0x03); // chargingState 未充电
        body.write(0x02); // runningMode 混动
        writeU16(body, 0);                  // 速度
        writeU32(body, 100_000L);           // 累计里程 = 10000 km
        writeU16(body, 5605);               // totalVoltage 560.5 V
        writeU16(body, 10000);              // totalCurrent 0 A (after -1000 offset)
        body.write(85);                     // SOC
        body.write(0x01);                   // DC-DC 工作
        body.write(0x00);                   // 挡位
        writeU16(body, 10000);              // 绝缘电阻
        body.write(0x00);                   // accelPedal
        body.write(0x00);                   // brakePedal

        // 0x05 位置数据 9B
        body.write(0x05);
        body.write(0x00);                   // 状态：有效定位
        writeU32(body, 120_848_158L);       // 经度 120.848158
        writeU32(body, 31_497_236L);        // 纬度 31.497236

        // 0x07 报警数据 10B：level=0 + flag=0 + 4 个空 fault list
        body.write(0x07);
        body.write(0x00);                   // maxLevel
        writeU32(body, 0);                  // generalFlag
        body.write(0x00);                   // batteryFaultCount
        body.write(0x00);                   // motorFaultCount
        body.write(0x00);                   // engineFaultCount
        body.write(0x00);                   // otherFaultCount

        // 0x08 储能电压：1 sub × 0 cell（保持简洁）
        body.write(0x08);
        body.write(0x01);                   // subCount
        body.write(0x01);                   // batteryIndex
        writeU16(body, 5605);               // voltage
        writeU16(body, 10000);              // current
        writeU16(body, 0);                  // cellCount
        writeU16(body, 1);                  // frameCellStart
        body.write(0x00);                   // frameCellCount = 0

        // 0x09 储能温度：1 sub × 0 probe
        body.write(0x09);
        body.write(0x01);                   // subCount
        body.write(0x01);                   // batteryIndex
        writeU16(body, 0);                  // probeCount = 0

        // 0x30 GuangdongFc.Stack：1 stack + 4 单体电压
        body.write(0x30);
        body.write(0x01);                   // stackCount
        body.write(0x01);                   // engineWorkState 打开
        body.write(90);                     // 水温 90-40 = 50°C
        writeU16(body, 1850);               // h2 入口压力 raw=1850 → 85 kPa
        writeU16(body, 1500);               // air 入口压力 raw=1500 → 50 kPa
        body.write(70);                     // air 入口温度 70-40 = 30°C
        writeU16(body, 12);                 // maxCellId
        writeU16(body, 88);                 // minCellId
        writeU16(body, 745);                // maxCellV 745 mV
        writeU16(body, 730);                // minCellV 730 mV
        writeU16(body, 738);                // avgCellV 738 mV
        writeU16(body, 400);                // cellCount
        writeU16(body, 1);                  // frameCellStart
        body.write(0x04);                   // frameCellCount = 4
        writeU16(body, 745);
        writeU16(body, 740);
        writeU16(body, 735);
        writeU16(body, 730);

        // 0x32 GuangdongFc.DcDc 9B
        body.write(0x32);
        writeU16(body, 3000);               // inputV 300.0
        writeU16(body, 1200);               // inputC 120.0
        writeU16(body, 5600);               // outputV 560.0
        writeU16(body, 1100);               // outputC 110.0
        body.write(105);                    // ctrlTemp 105-40 = 65°C

        // 0x33 GuangdongFc.AirConditioner 5B
        body.write(0x33);
        body.write(0x01);                   // status 启动
        writeU16(body, 20);                 // power 20-5 = 15 kw
        writeU16(body, 5400);               // compressorInputV 540.0

        // 0x34 GuangdongFc.VehicleInfo 7B
        body.write(0x34);
        body.write(0x00);                   // collision 无
        writeU16(body, 70);                 // ambientTemp 70-40 = 30°C
        writeU16(body, 1100);               // ambientPressure raw=1100 → 10.0 kPa
        writeU16(body, 80);                 // h2Mass 80*0.1 = 8.0 kg

        // 0x80 GuangdongFc.DemoExtension 12B（含 2B 内层 length=9）
        body.write(0x80);
        writeU16(body, 9);                  // declared length
        body.write(90);                     // stackTemp 50°C
        writeU16(body, 3720);               // ac voltage 372.0
        writeU16(body, 10000);              // ac current 0 A
        writeU16(body, 13);                 // h2 pump voltage 1.3
        writeU16(body, 10000);              // h2 pump current 0 A

        byte[] bodyBytes = body.toByteArray();

        // 帧封装
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write(0x23); out.write(0x23);
        out.write(0x02);                    // cmd realtime
        out.write(0xFE);                    // 命令包
        byte[] vin = "LNVFC000000000001".getBytes(StandardCharsets.US_ASCII);
        out.write(vin, 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));
    }

    // ====================================================================
    // Decoder 装配：手动构造一个挂着 guangdong-fc 路由的 BodyParser
    // ====================================================================
    private static Gb32960MessageDecoder buildDecoder() {
        var standardParsers = List.of(
                (com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser) new VehicleV2016BlockParser(),
                new DriveMotorV2016BlockParser(),
                new PositionV2016BlockParser(),
                new AlarmV2016BlockParser(),
                new VoltageV2016BlockParser(),
                new TemperatureV2016BlockParser());
        var catalog = new VendorExtensionCatalog(java.util.Map.of(
                "guangdong-fc", List.of(
                        new GdFcStackBlockParser(),
                        new GdFcAuxiliaryBlockParser(),
                        new GdFcDcDcBlockParser(),
                        new GdFcAirConditionerBlockParser(),
                        new GdFcVehicleInfoBlockParser(),
                        new GdFcDemoExtensionBlockParser())));
        var profileRegistry = new Gb32960ProfileRegistry(
                standardParsers, catalog, Set.of("guangdong-fc"));

        // selector：lingniu 账号 → guangdong-fc
        var entry = new Gb32960Properties.VendorExtension();
        entry.setName("guangdong-fc");
        var match = new Gb32960Properties.VendorExtension.Match();
        match.setPlatformAccounts(List.of("lingniu"));
        entry.setMatch(match);
        var selector = new RuleBasedVendorExtensionSelector(
                List.of(entry), Set.of("guangdong-fc"));

        var bodyParser = new Gb32960BodyParser(profileRegistry, selector);
        // PLATFORM_ACCOUNT_ATTR 仅供 handler 使用，这里不需要
        return new Gb32960MessageDecoder(bodyParser);
    }

    // suppress unused-import warning for the channel handler attr key
    @SuppressWarnings("unused")
    private static final Object KEEP_ATTR_REF = Gb32960ChannelHandler.PLATFORM_ACCOUNT_ATTR;
}
