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

import com.lingniu.ingest.protocol.gb32960.codec.Gb32960FrameEncoder;
import com.lingniu.ingest.protocol.gb32960.model.CommandType;
import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion;
import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.time.Instant;

/**
 * Builds and writes GB/T 32960 response frames.
 *
 * <p>ACK 需要尽量复用请求帧里的原始 17 字节 VIN。平台登入这类命令可能没有真实 VIN，
 * 如果把 trim 后的字符串重新 padding，部分上级平台会认为应答 VIN 与请求不一致。
 */
public final class Gb32960AckService {

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

    public void writeResponse(ChannelHandlerContext ctx,
                              ProtocolVersion version,
                              CommandType command,
                              ResponseFlag responseFlag,
                              byte[] rawVin,
                              Instant eventTime,
                              byte[] data,
                              String tag) {
        writeResponse(ctx.channel(), version, command, responseFlag, rawVin, eventTime, data, tag);
    }

    public void writeResponse(Channel channel,
                              ProtocolVersion version,
                              CommandType command,
                              ResponseFlag responseFlag,
                              byte[] rawVin,
                              Instant eventTime,
                              byte[] data,
                              String tag) {
        // rawVin 版本用于严格回显请求帧 VIN 字节，优先给入站 handler 使用。
        byte[] ack = Gb32960FrameEncoder.buildResponse(
                version, command, responseFlag, rawVin, eventTime, data);
        write(channel, ack, tag);
    }

    public void writeAndClose(ChannelHandlerContext ctx,
                              ProtocolVersion version,
                              CommandType command,
                              ResponseFlag responseFlag,
                              String vin,
                              Instant eventTime,
                              byte[] data,
                              String tag) {
        byte[] ack = Gb32960FrameEncoder.buildResponse(
                version, command, responseFlag, vin, eventTime, data);
        ctx.writeAndFlush(Unpooled.wrappedBuffer(ack)).addListener(f -> ctx.close());
    }

    public void writeAndClose(ChannelHandlerContext ctx,
                              ProtocolVersion version,
                              CommandType command,
                              ResponseFlag responseFlag,
                              byte[] rawVin,
                              Instant eventTime,
                              byte[] data,
                              String tag) {
        byte[] ack = Gb32960FrameEncoder.buildResponse(
                version, command, responseFlag, rawVin, eventTime, data);
        String hex = hex(ack);
        ctx.writeAndFlush(Unpooled.wrappedBuffer(ack)).addListener(f -> {
            if (f.isSuccess()) {
                log.info("[gb32960] {} flushed peer={} len={} hex={}",
                        tag, addr(ctx.channel()), ack.length, hex);
            } else {
                log.warn("[gb32960] {} flush FAILED peer={} len={} hex={}",
                        tag, addr(ctx.channel()), ack.length, hex, f.cause());
            }
            ctx.close();
        });
    }

    private static void write(Channel channel, byte[] ack, String tag) {
        String hex = hex(ack);
        boolean highFrequency = tag.startsWith("report-ack-") || tag.startsWith("heartbeat-ack");
        channel.writeAndFlush(Unpooled.wrappedBuffer(ack)).addListener(f -> {
            if (f.isSuccess()) {
                if (highFrequency) {
                    // 实时上报 ACK 频率高，默认降到 DEBUG，避免生产日志被正常心跳/上报刷满。
                    if (log.isDebugEnabled()) {
                        log.debug("[gb32960] {} flushed peer={} len={} hex={}",
                                tag, addr(channel), ack.length, hex);
                    }
                } else {
                    log.info("[gb32960] {} flushed peer={} len={} hex={}",
                            tag, addr(channel), ack.length, hex);
                }
            } else {
                log.warn("[gb32960] {} flush FAILED peer={} len={} hex={}",
                        tag, addr(channel), ack.length, hex, f.cause());
            }
        });
    }

    private static String hex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) sb.append(String.format("%02x", b));
        return sb.toString();
    }

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