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

import com.lingniu.ingest.api.spi.DecodeException;
import com.lingniu.ingest.protocol.gb32960.codec.profile.Gb32960ProfileRegistry;
import com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionSelector;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * 实时/补发上报数据单元循环解析器。
 *
 * <p>数据单元布局（表 7）：{@code timestamp(6B) | (typeFlag(1B) + infoBody)* | [0xFF 签名信息]}。
 * 其中时间戳由 {@link Gb32960MessageDecoder} 负责剥离，本 parser 只处理信息体循环。
 *
 * <p>支持的信息类型标志（表 9）：
 * <table>
 *   <tr><th>2016</th><th>2025</th><th>含义</th></tr>
 *   <tr><td>0x01</td><td>0x01</td><td>整车数据</td></tr>
 *   <tr><td>0x02</td><td>0x02</td><td>驱动电机数据</td></tr>
 *   <tr><td>0x03</td><td>0x03</td><td>燃料电池发动机及车载氢系统数据</td></tr>
 *   <tr><td>0x04</td><td>0x04</td><td>发动机数据</td></tr>
 *   <tr><td>0x05</td><td>0x05</td><td>车辆位置数据（2025 版增加坐标系字段）</td></tr>
 *   <tr><td>0x06 极值</td><td>0x06 报警</td><td>&nbsp;</td></tr>
 *   <tr><td>0x07 报警</td><td>0x07 动力蓄电池最小并联单元电压</td><td>&nbsp;</td></tr>
 *   <tr><td>0x08 储能电压</td><td>0x08 动力蓄电池温度</td><td>&nbsp;</td></tr>
 *   <tr><td>0x09 储能温度</td><td>—</td><td>&nbsp;</td></tr>
 *   <tr><td>—</td><td>0x30</td><td>燃料电池电堆</td></tr>
 *   <tr><td>—</td><td>0x31</td><td>超级电容器</td></tr>
 *   <tr><td>—</td><td>0x32</td><td>超级电容器极值</td></tr>
 *   <tr><td>—</td><td>0xFF</td><td>签名数据开始标识（2025 版新增）</td></tr>
 * </table>
 *
 * <p>处理策略：
 * <ul>
 *   <li>遇到 {@code 0xFF}（2025 版签名起始标识）：停止循环，剩余字节作为签名原始字节返回
 *   <li>遇到未注册的类型：作为单个 {@link InfoBlock.Raw} 包装剩余字节并终止循环，避免丢失整帧
 * </ul>
 */
public final class Gb32960BodyParser {

    private static final Logger log = LoggerFactory.getLogger(Gb32960BodyParser.class);

    /** 2025 版实时上报尾部签名标识（表 9）。 */
    private static final int SIGNATURE_MARKER = 0xFF;

    private final Gb32960ProfileRegistry profileRegistry;
    private final VendorExtensionSelector selector;

    /**
     * 单块解析异常时是否兜底为 {@link InfoBlock.Raw} 后继续。由
     * {@link com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties.Parse#isLenientBlockFailure()}
     * 注入；默认 true。实际行为在
     * {@link #parse(Gb32960ParserContext, ByteBuffer)} 主循环里实现。
     */
    private boolean lenientBlockFailure = true;

    public void setLenientBlockFailure(boolean lenientBlockFailure) {
        this.lenientBlockFailure = lenientBlockFailure;
    }

    /**
     * 单 registry 构造（向后兼容旧测试）。等价于 default profile + 无 vendor 扩展。
     */
    public Gb32960BodyParser(InfoBlockParserRegistry registry) {
        this.profileRegistry = new Gb32960ProfileRegistry(
                new java.util.ArrayList<>(),  // ignored, defaultRegistry overridden via wrapper below
                new com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionCatalog(java.util.Map.of()),
                java.util.Set.of()) {
            @Override public InfoBlockParserRegistry forProfile(String name) { return registry; }
            @Override public InfoBlockParserRegistry defaultRegistry() { return registry; }
        };
        this.selector = VendorExtensionSelector.NONE;
    }

    /**
     * 标准构造：profile 注册表 + vendor 选择器。运行期由
     * {@link Gb32960MessageDecoder} 调用 {@link #parse(Gb32960ParserContext, ByteBuffer)}
     * 携带上下文，按 selector 路由到具体 profile 的 registry。
     */
    public Gb32960BodyParser(Gb32960ProfileRegistry profileRegistry, VendorExtensionSelector selector) {
        this.profileRegistry = profileRegistry;
        this.selector = selector;
    }

    /** 旧入口：无上下文 → 走默认 profile。保留用于不需要 vendor 路由的单测。 */
    public Gb32960MessageDecoder.BodyParseResult parse(ProtocolVersion version, ByteBuffer body) {
        return parse(Gb32960ParserContext.minimal(version), body);
    }

    /** 主入口：根据上下文按 selector 选 vendor profile，然后照旧解析循环。 */
    public Gb32960MessageDecoder.BodyParseResult parse(Gb32960ParserContext ctx, ByteBuffer body) {
        ProtocolVersion version = ctx.version();
        // selector 只决定“标准 parser 集合上是否叠加某个 vendor 扩展集合”；标准 typeCode 始终可解析。
        InfoBlockParserRegistry registry = profileRegistry.forProfile(selector.select(ctx));
        List<InfoBlock> blocks = new ArrayList<>(8);
        byte[] signature = null;
        // 捕获 info-block 区间的起止位置，用于未知块出现时打 dump 帮助手动定位漂移点。
        // body 由 Gb32960MessageDecoder 通过 ByteBuffer.wrap(frame, bodyStart+6, dataLength-6) 构造，
        // arrayOffset()=0，position() = bodyStart+6，limit() = bodyStart + dataLength。
        final int infoBlocksStart = body.position();
        final int infoBlocksEnd = body.limit();

        while (body.hasRemaining()) {
            int typeStartPos = body.position();
            int typeCode = body.get() & 0xFF;

            if (typeCode == SIGNATURE_MARKER && version == ProtocolVersion.V2025) {
                // 剩余全部字节即为签名内容（表 8）
                signature = new byte[body.remaining()];
                body.get(signature);
                break;
            }

            InfoBlockParser parser = registry.find(version, typeCode);
            if (parser == null) {
                // lenient：未知类型无法安全跳过，吞剩余字节作为 Raw 后终止。
                // 真实 typeCode 透传到 Raw 字段，便于在日志/JSON 里直接看到漂移落点。
                // 如果该未知块实际是 TLV，应优先新增专用 TLV parser，让主循环可以继续解析后续块。
                int remaining = body.remaining();
                byte[] rest = new byte[remaining];
                body.get(rest);
                log.warn("[gb32960] unknown info block typeCode=0x{} version={} bodyPos={} parsedBlocks={} remaining={} preview={}",
                        Integer.toHexString(typeCode), version, typeStartPos,
                        blocks.size(), remaining, hexPreview(rest, 16));
                // 一次性把整段 info-block 区间的原始字节打出来，并在 typeStartPos 处插入标记，
                // 帮助人工对比规范字段长度、定位是哪个早期块对齐错。
                if (body.hasArray()) {
                    log.warn("[gb32960] body dump (info-block region) infoBlocksStart={} infoBlocksEnd={} typeStartPos={} totalLen={} alreadyParsed={}\n{}",
                            infoBlocksStart, infoBlocksEnd, typeStartPos,
                            infoBlocksEnd - infoBlocksStart, blocks.size(),
                            hexDumpWithMarker(body.array(), infoBlocksStart, infoBlocksEnd, typeStartPos));
                }
                blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, rest));
                break;
            }

            int fixedLen = parser.fixedLength();
            int posBefore = body.position();

            // 预检：固定长度块在剩余字节不足时，lenient 模式兜 Raw 后 break；严格模式保持旧行为抛异常。
            if (fixedLen >= 0 && body.remaining() < fixedLen) {
                if (!lenientBlockFailure) {
                    throw new DecodeException(
                            "info block 0x" + Integer.toHexString(typeCode)
                                    + " needs " + fixedLen + " bytes but got " + body.remaining());
                }
                // 固定长度块尾部缺字节时不猜测字段；保留剩余 bytes，方便后续用 RAW 回放复盘。
                int remaining = body.remaining();
                byte[] tail = new byte[remaining];
                body.get(tail);
                log.warn("[gb32960] truncated block typeCode=0x{} declaredFixedLen={} remaining={} — wrapping tail as Raw",
                        Integer.toHexString(typeCode), fixedLen, remaining);
                blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, tail));
                break;
            }

            InfoBlock block;
            try {
                block = parser.parse(body);
            } catch (DecodeException | java.nio.BufferUnderflowException | IndexOutOfBoundsException e) {
                if (!lenientBlockFailure) {
                    if (e instanceof DecodeException de) throw de;
                    throw new DecodeException(
                            "parser " + parser.getClass().getSimpleName() + " failed: " + e.getMessage(), e);
                }
                // 回滚到 parser.parse 入口，按块类型分支处理。
                body.position(posBefore);
                if (fixedLen >= 0) {
                    // 固定长度块：按 fixedLen 截取 → 兜 Raw → continue 循环
                    byte[] corrupt = new byte[fixedLen];
                    body.get(corrupt);
                    log.warn("[gb32960] block parse failed typeCode=0x{} parser={} fixedLen={} — isolated as Raw, continuing",
                            Integer.toHexString(typeCode), parser.getClass().getSimpleName(), fixedLen, e);
                    blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, corrupt));
                    continue;
                }
                // 变长块：无法安全找下一块边界 → 剩余整段兜 Raw + break
                int remaining = body.remaining();
                byte[] tail = new byte[remaining];
                body.get(tail);
                log.warn("[gb32960] variable-length block parse failed typeCode=0x{} parser={} remaining={} — wrapping remainder as Raw, stopping loop",
                        Integer.toHexString(typeCode), parser.getClass().getSimpleName(), remaining, e);
                blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, tail));
                break;
            }

            int consumed = body.position() - posBefore;
            if (fixedLen >= 0 && consumed != fixedLen) {
                // parser 契约违反（声明 fixedLen=X 但实际读了 Y），保持抛异常以暴露 parser bug，
                // 不走 lenient 兜底，避免静默误解析长期误判。
                throw new DecodeException(
                        "parser " + parser.getClass().getSimpleName()
                                + " consumed " + consumed
                                + " bytes but declared " + fixedLen);
            }
            if (log.isDebugEnabled()) {
                log.debug("[gb32960] block typeCode=0x{} parser={} pos {}→{} consumed={} declaredFixed={}",
                        Integer.toHexString(typeCode), parser.getClass().getSimpleName(),
                        typeStartPos, body.position(), consumed, fixedLen);
            }
            blocks.add(block);
        }

        return new Gb32960MessageDecoder.BodyParseResult(blocks, signature);
    }

    private static String hexPreview(byte[] bytes, int max) {
        int n = Math.min(bytes.length, max);
        StringBuilder sb = new StringBuilder(n * 2);
        for (int i = 0; i < n; i++) sb.append(String.format("%02x", bytes[i]));
        if (bytes.length > max) sb.append("..");
        return sb.toString();
    }

    /**
     * 16 字节/行的 hex dump，遇到 typeStartPos 时在该字节后插入 {@code |<<<HERE} 标记。
     * 行首打印绝对偏移（与 BodyParser 上下文里的 bodyPos 字段一致），方便逐字段倒查。
     */
    private static String hexDumpWithMarker(byte[] arr, int from, int toExclusive, int markerAbs) {
        StringBuilder sb = new StringBuilder((toExclusive - from) * 4);
        for (int i = from; i < toExclusive; i += 16) {
            sb.append(String.format("%04d:", i));
            for (int j = 0; j < 16 && i + j < toExclusive; j++) {
                int p = i + j;
                sb.append(' ').append(String.format("%02x", arr[p]));
                if (p == markerAbs) sb.append("<");
            }
            sb.append('\n');
        }
        return sb.toString();
    }
}
