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

import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.pipeline.RawFrame;
import com.lingniu.ingest.core.dispatcher.Dispatcher;
import com.lingniu.ingest.protocol.gb32960.auth.Gb32960PlatformAuthorizer;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder;
import com.lingniu.ingest.protocol.gb32960.model.CommandBody;
import com.lingniu.ingest.protocol.gb32960.model.CommandType;
import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message;
import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Netty 入站处理器：字节 → Gb32960Message → 认证 → RawFrame → Dispatcher。
 *
 * <p>这是 32960 生产链路的入口边界。它只做协议层职责：解码、鉴权、应答、组装
 * {@link RawFrame} 并投递 Dispatcher；原始包归档、TDengine 历史索引、Kafka 转发都在
 * 下游 sink 中完成，避免 Netty EventLoop 被存储 IO 阻塞。
 *
 * <p>认证逻辑：
 * <ul>
 *   <li>{@link Gb32960VinAuthorizer#isEnforcing()} = false：放行所有 VIN
 *   <li>{@link Gb32960VinAuthorizer#isEnforcing()} = true：
 *     <ul>
 *       <li>车辆登入 {@code 0x01}：VIN 在白名单则返回成功应答（保留原帧时间）并派发；
 *           不在则返回 {@code 0x04 VIN_NOT_EXIST} 应答并关闭连接
 *       <li>其他命令：VIN 在白名单则派发；不在则 DEBUG 日志后关闭连接
 *     </ul>
 * </ul>
 * 注意应答帧由 {@link Gb32960AckService} 构造并写回 Channel。
 */
public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {

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

    /**
     * 0x05 PLATFORM_LOGIN 鉴权通过后写入；后续 0x02/0x03 实时上报帧从 channel attribute 取出
     * 作为 {@code Gb32960ParserContext.platformAccount}，供 vendor profile selector 路由。
     * 普通车端直连场景（0x01 VEHICLE_LOGIN）此 attribute 始终为 null。
     */
    public static final AttributeKey<String> PLATFORM_ACCOUNT_ATTR =
            Gb32960AccessService.PLATFORM_ACCOUNT_ATTR;

    private static final AttributeKey<CompletableFuture<Void>> DURABLE_ACK_CHAIN_ATTR =
            AttributeKey.valueOf("gb32960.durableAckChain");

    private final Gb32960MessageDecoder decoder;
    private final Dispatcher dispatcher;
    private final Gb32960AccessService accessService;
    private final Gb32960AckService ackService;
    private final Gb32960FrameDiagnostics diagnostics;
    private final Gb32960ConnectionDiagnostics connectionDiagnostics;

    public Gb32960ChannelHandler(Gb32960MessageDecoder decoder,
                                 Dispatcher dispatcher,
                                 Gb32960AccessService accessService,
                                 Gb32960AckService ackService,
                                 Gb32960FrameDiagnostics diagnostics,
                                 Gb32960ConnectionDiagnostics connectionDiagnostics) {
        this.decoder = decoder;
        this.dispatcher = dispatcher;
        this.accessService = accessService;
        this.ackService = ackService;
        this.diagnostics = diagnostics;
        this.connectionDiagnostics = connectionDiagnostics;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        connectionDiagnostics.opened(sessionId(ctx), addr(ctx));
        log.info("[gb32960] connection opened peer={}", addr(ctx));
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        connectionDiagnostics.closed(sessionId(ctx));
        log.info("[gb32960] connection closed peer={}", addr(ctx));
    }

    /**
     * 读空闲告警：仅记录日志，不断链。用于排查"对端登入成功后长时间不推数据"场景。
     * 由 pipeline 中的 {@link io.netty.handler.timeout.IdleStateHandler}（可选）触发。
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent idle && idle.state() == IdleState.READER_IDLE) {
            connectionDiagnostics.readIdle(sessionId(ctx));
            log.warn("[gb32960] read idle peer={} noInboundFrameFor>=thresholdSeconds (see idleReadSeconds)",
                    addr(ctx));
        }
        super.userEventTriggered(ctx, evt);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, byte[] frame) {
        Gb32960Message msg;
        try {
            String platformAccount = accessService.platformAccount(ctx.channel());
            msg = decoder.decode(ByteBuffer.wrap(frame), platformAccount);
        } catch (Exception e) {
            // 解码失败只丢当前帧，不主动断开连接。真实设备偶发脏字节时，FrameDecoder 会继续寻找下一帧头。
            log.warn("[gb32960] decode failed peer={} len={}", addr(ctx), frame.length, e);
            dispatchMalformed(ctx, frame, e.getMessage());
            return;
        }

        String vin = msg.header().vin();
        CommandType cmd = msg.header().command();
        connectionDiagnostics.received(sessionId(ctx), cmd, vin);
        // 原样回显请求帧的 17 字节 VIN（frame[4..20]），避免平台登入 VIN 全 0
        // 被 vinPadded 重写为空格导致对端判定 VIN 不匹配。
        byte[] rawVin = new byte[17];
        System.arraycopy(frame, 4, rawVin, 0, 17);

        // 平台登入/登出（0x05/0x06）没有 VIN，不进 VIN 白名单校验
        boolean isPlatformCommand = cmd == CommandType.PLATFORM_LOGIN || cmd == CommandType.PLATFORM_LOGOUT;
        if (!isPlatformCommand && !accessService.isVinAllowed(vin)) {
            handleUnauthorized(ctx, msg);
            return;
        }

        // 鉴权通过：先完成命令侧状态变更/日志，再统一 dispatch 到通用管线。
        // 需要成功 ACK 的已接收帧，等待配置的 Kafka dispatch 边界完成后再回 ACK。
        // 平台登入鉴权失败会短路 return，不进 dispatch。
        switch (cmd) {
            case VEHICLE_LOGIN -> logVehicleLogin(ctx, msg);
            case VEHICLE_LOGOUT -> logVehicleLogout(ctx, msg);
            case PLATFORM_LOGIN -> {
                if (!handlePlatformLogin(ctx, msg, rawVin)) return;
            }
            case PLATFORM_LOGOUT -> logPlatformLogout(ctx);
            case REALTIME_REPORT, RESEND_REPORT, HEARTBEAT -> logReportOrHeartbeat(ctx, msg);
            case TIME_CALIBRATION -> { }
            default -> logOtherFrame(ctx, msg);
        }

        RawFrame rf = rawFrame(ctx, msg, frame);
        if (!requiresDurableAck(cmd)) {
            dispatcher.dispatch(rf);
            return;
        }

        enqueueDurableAck(ctx, msg, rawVin, vin, cmd, dispatcher.dispatchAndAwait(rf));
    }

    private void enqueueDurableAck(ChannelHandlerContext ctx,
                                   Gb32960Message msg,
                                   byte[] rawVin,
                                   String vin,
                                   CommandType cmd,
                                   CompletableFuture<Void> dispatchFuture) {
        CompletableFuture<Void> previous = ctx.channel().attr(DURABLE_ACK_CHAIN_ATTR).get();
        if (previous == null) {
            previous = CompletableFuture.completedFuture(null);
        }

        CompletableFuture<Void> next = previous
                .handle((ignored, previousFailure) -> null)
                .thenCompose(ignored -> dispatchFuture.handle((ok, failure) -> failure))
                .thenCompose(failure -> runOnEventLoop(ctx,
                        () -> handleDurableAckResult(ctx, msg, rawVin, vin, cmd, failure)));
        ctx.channel().attr(DURABLE_ACK_CHAIN_ATTR).set(next);
    }

    private CompletableFuture<Void> runOnEventLoop(ChannelHandlerContext ctx, Runnable task) {
        CompletableFuture<Void> done = new CompletableFuture<>();
        Runnable wrapped = () -> {
            try {
                task.run();
                done.complete(null);
            } catch (Throwable t) {
                done.completeExceptionally(t);
            }
        };
        if (ctx.executor().inEventLoop()) {
            wrapped.run();
        } else {
            ctx.executor().execute(wrapped);
        }
        return done;
    }

    private void handleDurableAckResult(ChannelHandlerContext ctx,
                                        Gb32960Message msg,
                                        byte[] rawVin,
                                        String vin,
                                        CommandType cmd,
                                        Throwable failure) {
        if (!ctx.channel().isActive()) {
            return;
        }
        if (failure != null) {
            log.warn("[gb32960] dispatch failed before ack peer={} vin={} cmd=0x{}",
                    addr(ctx), vin, Integer.toHexString(cmd.code()), failure);
            ctx.close();
            return;
        }
        writeAckForCommand(ctx, msg, rawVin);
    }

    private RawFrame rawFrame(ChannelHandlerContext ctx, Gb32960Message msg, byte[] frame) {
        Map<String, String> sourceMeta = new HashMap<>(4);
        sourceMeta.put("vin", msg.header().vin());
        sourceMeta.put("peer", addr(ctx));
        String platformAccount = accessService.platformAccount(ctx.channel());
        if (platformAccount != null && !platformAccount.isBlank()) {
            sourceMeta.put("platformAccount", platformAccount);
        }
        // RawFrame.rawBytes 是后续 raw archive 和按 rawArchiveUri 回查的源头；不要在这里裁剪或重编码。
        return new RawFrame(
                ProtocolId.GB32960,
                msg.header().command().code(),
                0,
                msg,
                frame,
                sourceMeta,
                Instant.now());
    }

    private void dispatchMalformed(ChannelHandlerContext ctx, byte[] frame, String errorMessage) {
        Map<String, String> sourceMeta = new HashMap<>(4);
        sourceMeta.put("vin", "unknown");
        sourceMeta.put("peer", addr(ctx));
        sourceMeta.put("parseError", "true");
        sourceMeta.put("parseErrorMessage", errorMessage == null ? "" : errorMessage);
        dispatcher.dispatch(new RawFrame(
                ProtocolId.GB32960,
                0,
                0,
                frame,
                frame == null ? new byte[0] : frame,
                sourceMeta,
                Instant.now()));
    }

    private static boolean requiresDurableAck(CommandType cmd) {
        return switch (cmd) {
            case VEHICLE_LOGIN,
                 VEHICLE_LOGOUT,
                 PLATFORM_LOGIN,
                 PLATFORM_LOGOUT,
                 REALTIME_REPORT,
                 RESEND_REPORT,
                 HEARTBEAT,
                 TIME_CALIBRATION -> true;
            default -> false;
        };
    }

    private void writeAckForCommand(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
        switch (msg.header().command()) {
            case VEHICLE_LOGIN -> writeVehicleLoginAck(ctx, msg, rawVin);
            case VEHICLE_LOGOUT -> writeVehicleLogoutAck(ctx, msg, rawVin);
            case PLATFORM_LOGIN -> writePlatformLoginAck(ctx, msg, rawVin);
            case PLATFORM_LOGOUT -> writePlatformLogoutAck(ctx, msg, rawVin);
            case REALTIME_REPORT, RESEND_REPORT, HEARTBEAT -> writeReportOrHeartbeatAck(ctx, msg, rawVin);
            case TIME_CALIBRATION -> writeTimeCalibrationAck(ctx, msg, rawVin);
            default -> { }
        }
    }

    /** 0x01 车辆登入：按 §6.3.2 带原采集时间回成功 ACK + INFO 日志。 */
    private void logVehicleLogin(ChannelHandlerContext ctx, Gb32960Message msg) {
        log.info("[gb32960] vehicle login peer={} vin={} protocolVersion={}",
                addr(ctx), msg.header().vin(), msg.header().protocolVersion());
    }

    private void writeVehicleLoginAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
        ackService.writeResponse(
                ctx,
                msg.header().protocolVersion(),
                CommandType.VEHICLE_LOGIN,
                ResponseFlag.SUCCESS,
                rawVin,
                eventOrNow(msg),
                null,
                "vehicle-login-ack");
    }

    /** 0x04 车辆登出：INFO 日志 + 带原采集时间回成功 ACK。 */
    private void logVehicleLogout(ChannelHandlerContext ctx, Gb32960Message msg) {
        log.info("[gb32960] vehicle logout peer={} vin={}", addr(ctx), msg.header().vin());
    }

    private void writeVehicleLogoutAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
        ackService.writeResponse(ctx,
                msg.header().protocolVersion(),
                CommandType.VEHICLE_LOGOUT,
                ResponseFlag.SUCCESS,
                rawVin,
                eventOrNow(msg),
                null,
                "vehicle-logout-ack");
    }

    /**
     * 0x05 平台登入（表 29）：含 12B username + 20B password + 加密规则。
     *
     * <ul>
     *   <li>消息体解析缺失：关闭连接，返回 false 短路
     *   <li>鉴权失败：写 {@code 0x02 OTHER_ERROR} NACK + 关闭连接，返回 false 短路
     *   <li>鉴权通过：把 username 钉到 channel attribute（供后续 vendor profile 路由），INFO 日志，
     *       返回 true 继续 dispatch；成功 ACK 等 dispatch 持久化边界完成后再写
     * </ul>
     *
     * @return true 表示鉴权通过应继续 dispatch；false 表示调用方应 short-circuit return
     */
    private boolean handlePlatformLogin(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
        if (!(msg.commandBody() instanceof CommandBody.PlatformLogin pl)) {
            log.warn("[gb32960] platform login peer={} body unparsed, closing", addr(ctx));
            ctx.close();
            return false;
        }
        Gb32960PlatformAuthorizer.Result result = accessService.authenticatePlatformLogin(
                pl.username(), pl.password(), ctx.channel());
        if (!result.accepted()) {
            log.warn("[gb32960] platform login REJECTED peer={} username={} reason={}",
                    addr(ctx), pl.username(), result);
            ackService.writeAndClose(
                    ctx,
                    msg.header().protocolVersion(),
                    CommandType.PLATFORM_LOGIN,
                    ResponseFlag.OTHER_ERROR,
                    rawVin,
                    eventOrNow(msg),
                    null,
                    "platform-login-nack");
            return false;
        }
        log.info("[gb32960] platform login peer={} username={} encryptRule={} serial={} time={} policy={}",
                addr(ctx), pl.username(), pl.encryptRule(), pl.serialNo(), pl.loginTime(), result);
        // 把 username 钉到 channel attribute，供后续 0x02/0x03 帧的 vendor profile 路由
        accessService.bindPlatformAccount(ctx.channel(), pl.username());
        connectionDiagnostics.platformLoginAccepted(sessionId(ctx), pl.username());
        return true;
    }

    private void writePlatformLoginAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
        ackService.writeResponse(
                ctx,
                msg.header().protocolVersion(),
                CommandType.PLATFORM_LOGIN,
                ResponseFlag.SUCCESS,
                rawVin,
                eventOrNow(msg),
                null,
                "platform-login-ack");
    }

    /** 0x06 平台登出：INFO 日志 + 带原采集时间回成功 ACK。 */
    private void logPlatformLogout(ChannelHandlerContext ctx) {
        log.info("[gb32960] platform logout peer={}", addr(ctx));
    }

    private void writePlatformLogoutAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
        ackService.writeResponse(ctx,
                msg.header().protocolVersion(),
                CommandType.PLATFORM_LOGOUT,
                ResponseFlag.SUCCESS,
                rawVin,
                eventOrNow(msg),
                null,
                "platform-logout-ack");
    }

    /**
     * 0x02/0x03/0x07 实时 / 补发 / 心跳：记录帧诊断；成功 ACK 在配置的 Kafka dispatch 边界后写回。
     * 部分车端实现严格按规范，没收到应答会停止后续上报或重连。ack-tag 按 cmd.code 区分便于日志筛选。
     *
     * <p>注意：应答成功表示当前配置的 Kafka sink 已完成该帧映射事件的 dispatch 边界。
     * 生产排查历史数据问题时，应继续检查 raw archive sink、Kafka topic 和 history app 的
     * TDengine raw_frames 写入日志。
     *
     * <p>日志策略（按 (channel, vin, rawTypeSignature) 去重，避免高频帧刷屏）：
     * <ul>
     *   <li>0x02 / 0x03 首次见到某 (vin, normal) → INFO 一条，含 peer/vin/platformAccount/version/parsedBlocks。
     *   <li>0x02 / 0x03 首次见到某 (vin, rawTypes=…) → WARN 一条，列出 raw typeCode。提示对端可能没走预期
     *       vendor profile（例：peer 是广东燃料电池但 platformAccount 没匹配上
     *       {@code vendor-extensions.platform-accounts} → 0x30+ 落 Raw）。
     *   <li>同 (vin, signature) 后续帧 → DEBUG（默认看不到；troubleshoot 时打开 Gb32960ChannelHandler 的 DEBUG）。
     *   <li>0x07 心跳：DEBUG（量大且无信息体）。
     * </ul>
     * 帧内字段全量 JSON 仍保留 DEBUG。
     */
    private void logReportOrHeartbeat(ChannelHandlerContext ctx, Gb32960Message msg) {
        CommandType cmd = msg.header().command();
        String platformAccount = accessService.platformAccount(ctx.channel());
        if (cmd == CommandType.HEARTBEAT) {
            if (log.isDebugEnabled()) {
                log.debug("[gb32960] HEARTBEAT peer={} vin={} platformAccount={}",
                        addr(ctx), msg.header().vin(), platformAccount);
            }
            return;
        }
        List<String> rawTypes = Gb32960FrameDiagnostics.collectRawTypeHex(msg.infoBlocks());
        int parsedBlocks = msg.infoBlocks().size() - rawTypes.size();
        String vin = msg.header().vin();
        boolean firstSeen = diagnostics.markFirstSeen(ctx.channel(), vin, rawTypes);
        if (firstSeen) {
            if (rawTypes.isEmpty()) {
                log.info("[gb32960] {} (first frame for vin) peer={} vin={} platformAccount={} version={} parsedBlocks={}",
                        cmd.name(), addr(ctx), vin, platformAccount,
                        msg.header().protocolVersion(), parsedBlocks);
            } else {
                log.warn("[gb32960] {} peer={} vin={} platformAccount={} version={} parsedBlocks={} rawTypes={} — vendor profile likely not selected for this peer",
                        cmd.name(), addr(ctx), vin, platformAccount,
                        msg.header().protocolVersion(), parsedBlocks, rawTypes);
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("[gb32960] frame peer={} vin={} cmd=0x{} parsedBlocks={} rawTypes={} json={}",
                    addr(ctx), vin, Integer.toHexString(cmd.code()),
                    parsedBlocks, rawTypes, Gb32960FrameDiagnostics.toJson(msg));
        }
    }

    private void writeReportOrHeartbeatAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
        CommandType cmd = msg.header().command();
        ackService.writeResponse(ctx,
                msg.header().protocolVersion(),
                cmd,
                ResponseFlag.SUCCESS,
                rawVin,
                eventOrNow(msg),
                null,
                "report-ack-0x" + Integer.toHexString(cmd.code()));
    }

    /** 0x08 终端校时：平台应答 data 段为平台当前时间 6B（用 Instant.now()）。 */
    private void writeTimeCalibrationAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
        ackService.writeResponse(ctx,
                msg.header().protocolVersion(),
                CommandType.TIME_CALIBRATION,
                ResponseFlag.SUCCESS,
                rawVin,
                Instant.now(),
                null,
                "time-calibration-ack");
    }

    /** 其它未单独处理的命令（如激活 0x09、密钥交换 0x0A）：仅 DEBUG 日志，仍会进 dispatcher。 */
    private void logOtherFrame(ChannelHandlerContext ctx, Gb32960Message msg) {
        if (log.isDebugEnabled()) {
            log.debug("[gb32960] frame peer={} vin={} cmd=0x{} infoBlocks={} json={}",
                    addr(ctx), msg.header().vin(), Integer.toHexString(msg.header().command().code()),
                    msg.infoBlocks().size(), Gb32960FrameDiagnostics.toJson(msg));
        }
    }

    /** 优先使用帧内采集时间；为 null 时回落到 {@link Instant#now()}。 */
    private static Instant eventOrNow(Gb32960Message msg) {
        return msg.header().eventTime() != null ? msg.header().eventTime() : Instant.now();
    }

    /** VIN 未授权：对登入命令下发 VIN_NOT_EXIST 应答；其他命令静默关闭。 */
    private void handleUnauthorized(ChannelHandlerContext ctx, Gb32960Message msg) {
        String vin = msg.header().vin();
        CommandType cmd = msg.header().command();
        if (cmd == CommandType.VEHICLE_LOGIN) {
            // 规范要求登入失败要回 ACK，让终端能明确知道 VIN 不存在，而不是当作网络超时重试。
            ackService.writeAndClose(
                    ctx,
                    msg.header().protocolVersion(),
                    CommandType.VEHICLE_LOGIN,
                    ResponseFlag.VIN_NOT_EXIST,
                    vin,
                    msg.header().eventTime() != null ? msg.header().eventTime() : Instant.now(),
                    null,
                    "vehicle-login-nack");
            log.info("[gb32960] reject login peer={} vin={} (not in whitelist)", addr(ctx), vin);
        } else {
            log.debug("[gb32960] drop frame peer={} vin={} cmd=0x{} (not in whitelist)",
                    addr(ctx), vin, Integer.toHexString(cmd.code()));
            ctx.close();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.warn("[gb32960] channel error peer={}", addr(ctx), cause);
        ctx.close();
    }

    private static String addr(ChannelHandlerContext ctx) {
        if (ctx.channel().remoteAddress() instanceof InetSocketAddress i) {
            return i.getAddress().getHostAddress() + ":" + i.getPort();
        }
        return "unknown";
    }

    private static String sessionId(ChannelHandlerContext ctx) {
        return ctx.channel().id().asLongText();
    }

}
