package com.cmvr.quic.gateway.core;

import com.cmvr.quic.edge.v1.DeviceManagerSnapshot;
import com.cmvr.quic.edge.v1.EdgeControlEnvelope;
import com.cmvr.quic.edge.v1.GrpcEndpoint;
import com.cmvr.quic.edge.v1.ManagedDeviceStatus;
import com.cmvr.quic.edge.v1.MediaSessionOpen;
import com.cmvr.quic.edge.v1.MediaTrackDescriptor;
import com.cmvr.quic.edge.v1.NetworkInterfaceAddress;
import com.cmvr.quic.edge.v1.NodeDescriptor;
import com.cmvr.quic.edge.v1.NodeHeartbeat;
import com.cmvr.quic.edge.v1.NodeRegisterRequest;
import com.cmvr.quic.gateway.config.GatewayConfig;
import com.cmvr.quic.gateway.protocol.DatagramHeader;
import com.cmvr.quic.gateway.protocol.MediaReassembler;
import com.cmvr.quic.gateway.v1.MediaFrame;
import com.cmvr.quic.gateway.v1.MediaTrack;
import com.cmvr.quic.gateway.v1.NodeEventType;
import com.cmvr.quic.gateway.v1.NodeSnapshot;
import com.google.protobuf.ByteString;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.quic.QuicChannel;
import io.netty.handler.codec.quic.QuicStreamChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;

/** 单条QUIC连接的协议状态，所有媒体重组缓冲区均按连接隔离。 */
public final class ConnectionSession {

    private static final Logger log = LoggerFactory.getLogger(ConnectionSession.class);
    private static final int PROTOCOL_VERSION = 1;
    private static final int PROTOCOL_ERROR_CODE = 1001;
    private static final long HEARTBEAT_LOG_INTERVAL = 6L;

    private final QuicChannel channel;
    private final GatewayConfig config;
    private final NodeEventBroker broker;
    private final String observedSourceIp;
    private final MediaReassembler reassembler;
    private final Map<Integer, MediaTrackDescriptor> tracks =
            new LinkedHashMap<Integer, MediaTrackDescriptor>();
    private final AtomicLong invalidDatagrams = new AtomicLong();

    private QuicStreamChannel controlStream;
    private boolean firstMessageReceived;
    private boolean inboundSequenceReceived;
    private long inboundSequence;
    private long outboundSequence;
    private boolean registered;
    private boolean offlinePublished;
    private boolean heartbeatTimeoutClosing;
    private String nodeId = "";
    private String bootId = "";
    private String sessionId = "";
    private String softwareVersion = "";
    private long registeredAtMs;
    private long lastHeartbeatAtMs;
    private long heartbeatSequence;
    private long heartbeatMessagesReceived;
    private String lastDeviceSnapshotSignature = "";
    private long mediaSessionEpoch;
    private java.util.List<NetworkInterfaceAddress> localInterfaces =
            java.util.Collections.emptyList();
    private GrpcEndpoint grpcEndpoint = GrpcEndpoint.getDefaultInstance();
    private DeviceManagerSnapshot deviceManager = DeviceManagerSnapshot.getDefaultInstance();

    public ConnectionSession(QuicChannel channel, GatewayConfig config, NodeEventBroker broker) {
        this.channel = channel;
        this.config = config;
        this.broker = broker;
        this.observedSourceIp = remoteIp(channel.remoteSocketAddress());
        this.reassembler = new MediaReassembler(config.getMaximumReassemblyBytes(),
                config.getMaximumReassemblyFrames(), config.getMaximumFrameBytes(),
                config.getReassemblyTimeoutMs());
    }

    public synchronized boolean attachControlStream(QuicStreamChannel stream) {
        if (controlStream != null) {
            return false;
        }
        controlStream = stream;
        return true;
    }

    public synchronized void process(EdgeControlEnvelope envelope) {
        if (envelope.getProtocolVersion() != PROTOCOL_VERSION) {
            protocolViolation("不支持的QUIC edge协议版本", envelope.getMessageSequence());
            return;
        }
        long sequence = envelope.getMessageSequence();
        if (inboundSequenceReceived && Long.compareUnsigned(sequence, inboundSequence) <= 0) {
            protocolViolation("控制消息序列号未严格递增", sequence);
            return;
        }
        inboundSequenceReceived = true;
        inboundSequence = sequence;
        if (!firstMessageReceived && envelope.getPayloadCase()
                != EdgeControlEnvelope.PayloadCase.NODE_REGISTER_REQUEST) {
            protocolViolation("首条控制消息必须是NodeRegisterRequest", sequence);
            return;
        }
        firstMessageReceived = true;

        switch (envelope.getPayloadCase()) {
            case NODE_REGISTER_REQUEST:
                handleRegistration(envelope.getNodeRegisterRequest(), sequence);
                break;
            case NODE_HEARTBEAT:
                handleHeartbeat(envelope.getNodeHeartbeat(), sequence);
                break;
            case MEDIA_SESSION_OPEN:
                handleMediaSessionOpen(envelope.getMediaSessionOpen(), sequence);
                break;
            case MEDIA_TRACK_DESCRIPTOR:
                handleTrackDescriptor(envelope.getMediaTrackDescriptor(), sequence);
                break;
            default:
                protocolViolation("终端发送了当前Gateway不接受的控制消息", sequence);
        }
    }

    private void handleRegistration(NodeRegisterRequest request, long relatedSequence) {
        NodeDescriptor node = request.getNode();
        if (registered || node.getNodeId().isEmpty() || node.getBootId().isEmpty()
                || node.getNodeId().length() > 256 || node.getBootId().length() > 256
                || node.getGrpcEndpoint().getPort() > 65535) {
            protocolViolation("终端注册信息不合法或重复注册", relatedSequence);
            return;
        }
        if (!config.isNodeAllowed(node.getNodeId())) {
            protocolViolation("终端nodeId不在Gateway准入名单中", relatedSequence);
            return;
        }
        registered = true;
        nodeId = node.getNodeId();
        bootId = node.getBootId();
        sessionId = UUID.randomUUID().toString();
        softwareVersion = node.getSoftwareVersion();
        localInterfaces = node.getLocalInterfacesList();
        grpcEndpoint = node.getGrpcEndpoint();
        registeredAtMs = System.currentTimeMillis();

        broker.publishNode(NodeEventType.NODE_EVENT_TYPE_REGISTERED, snapshot(true));
        EdgeControlEnvelope response = EdgeControlEnvelope.newBuilder()
                .setProtocolVersion(PROTOCOL_VERSION)
                .setMessageSequence(nextOutboundSequence())
                .setNodeRegisterResponse(com.cmvr.quic.edge.v1.NodeRegisterResponse.newBuilder()
                        .setAccepted(true)
                        .setSessionId(sessionId)
                        .setMessage("accepted by cmvr Java QUIC gateway")
                        .setHeartbeatIntervalMs(config.getHeartbeatIntervalMs())
                        .setObservedSourceIp(observedSourceIp))
                .build();
        send(response);
        log.info("QUIC终端注册成功，nodeId={}，bootId={}，sessionId={}，remoteIp={}，"
                        + "grpcAdvertised={}:{}，grpcEffective={}:{}",
                nodeId, bootId, sessionId, observedSourceIp,
                grpcEndpoint.getHost(), grpcEndpoint.getPort(),
                observedSourceIp, grpcEndpoint.getPort());
    }

    private void handleHeartbeat(NodeHeartbeat heartbeat, long relatedSequence) {
        if (!registered || !nodeId.equals(heartbeat.getNodeId())
                || !bootId.equals(heartbeat.getBootId())
                || !sessionId.equals(heartbeat.getSessionId())
                || (heartbeatSequence != 0
                && Long.compareUnsigned(heartbeat.getSequence(), heartbeatSequence) <= 0)) {
            protocolViolation("心跳与当前注册会话不匹配", relatedSequence);
            return;
        }
        heartbeatSequence = heartbeat.getSequence();
        lastHeartbeatAtMs = System.currentTimeMillis();
        softwareVersion = heartbeat.getSoftwareVersion();
        localInterfaces = heartbeat.getLocalInterfacesList();
        grpcEndpoint = heartbeat.getGrpcEndpoint();
        if (heartbeat.hasDeviceManager()) {
            deviceManager = heartbeat.getDeviceManager();
        }
        heartbeatMessagesReceived++;
        logHeartbeatIfNeeded(heartbeat);
        logDeviceSnapshotIfChanged();
        broker.publishNode(NodeEventType.NODE_EVENT_TYPE_HEARTBEAT, snapshot(true));
        send(EdgeControlEnvelope.newBuilder()
                .setProtocolVersion(PROTOCOL_VERSION)
                .setMessageSequence(nextOutboundSequence())
                .setNodeHeartbeatAck(com.cmvr.quic.edge.v1.NodeHeartbeatAck.newBuilder()
                        .setAccepted(true)
                        .setAcknowledgedSequence(heartbeat.getSequence())
                        .setMessage("ok")
                        .setServerTimeUnixMs(System.currentTimeMillis())
                        .setObservedSourceIp(observedSourceIp)
                        .setSessionId(sessionId))
                .build());
    }

    /** 首个心跳立即记录，后续低频记录摘要，避免默认5秒心跳持续刷屏。 */
    private void logHeartbeatIfNeeded(NodeHeartbeat heartbeat) {
        if (heartbeatMessagesReceived != 1
                && heartbeatMessagesReceived % HEARTBEAT_LOG_INTERVAL != 0) {
            return;
        }
        log.info("收到QUIC终端心跳，nodeId={}，sessionId={}，count={}，sequence={}，"
                        + "sentAtUnixMs={}，softwareVersion={}，interfaces={}，"
                        + "grpcAdvertised={}:{}，grpcEffective={}:{}，devices={}",
                nodeId, sessionId, heartbeatMessagesReceived, heartbeat.getSequence(),
                heartbeat.getSentAtUnixMs(), softwareVersion, localInterfaces.size(),
                grpcEndpoint.getHost(), grpcEndpoint.getPort(), observedSourceIp,
                grpcEndpoint.getPort(), deviceManager.getDevicesCount());
    }

    /** 设备快照内容发生变化时打印设备明细，采样时间变化本身不视为状态变化。 */
    private void logDeviceSnapshotIfChanged() {
        String signature = deviceSnapshotSignature(deviceManager);
        if (signature.equals(lastDeviceSnapshotSignature)) {
            return;
        }
        lastDeviceSnapshotSignature = signature;
        log.info("收到QUIC设备快照，nodeId={}，managerName={}，managerVersion={}，"
                        + "sampledAtUnixMs={}，deviceCount={}",
                nodeId, deviceManager.getManagerName(), deviceManager.getManagerVersion(),
                deviceManager.getSampledAtUnixMs(), deviceManager.getDevicesCount());
        for (ManagedDeviceStatus device : deviceManager.getDevicesList()) {
            log.info("QUIC设备信息，nodeId={}，deviceId={}，kind={}，typeName={}，enabled={}，"
                            + "managerState={}，health={}，hasError={}，errorMessage={}，updatedAtUnixMs={}",
                    nodeId, device.getDeviceId(), device.getKind(), device.getTypeName(),
                    device.getEnabled(), device.getManagerState(), device.getHealth(),
                    device.getHasError(), device.getErrorMessage(),
                    device.getStatusUpdatedAtUnixMs());
        }
    }

    private static String deviceSnapshotSignature(DeviceManagerSnapshot snapshot) {
        StringBuilder value = new StringBuilder(256);
        value.append(snapshot.getManagerName()).append('\u001f')
                .append(snapshot.getManagerVersion()).append('\u001f')
                .append(snapshot.getManagerDescription());
        for (ManagedDeviceStatus device : snapshot.getDevicesList()) {
            value.append('\u001e').append(device.getDeviceId())
                    .append('\u001f').append(device.getKindValue())
                    .append('\u001f').append(device.getTypeName())
                    .append('\u001f').append(device.getEnabled())
                    .append('\u001f').append(device.getManagerStateValue())
                    .append('\u001f').append(device.getHealthValue())
                    .append('\u001f').append(device.getHasError())
                    .append('\u001f').append(device.getErrorMessage())
                    .append('\u001f').append(device.getStatusUpdatedAtUnixMs());
        }
        return value.toString();
    }

    private void handleMediaSessionOpen(MediaSessionOpen open, long relatedSequence) {
        if (!registered || !nodeId.equals(open.getNodeId())
                || !sessionId.equals(open.getSessionId()) || open.getSessionEpoch() == 0
                || mediaSessionEpoch != 0) {
            protocolViolation("媒体会话信息不合法", relatedSequence);
            return;
        }
        mediaSessionEpoch = open.getSessionEpoch();
        tracks.clear();
        reassembler.reset(mediaSessionEpoch);
        broker.publishNode(NodeEventType.NODE_EVENT_TYPE_MEDIA_CHANGED, snapshot(true));
        log.info("QUIC媒体会话已打开，nodeId={}，sessionEpoch={}", nodeId, mediaSessionEpoch);
    }

    private void handleTrackDescriptor(MediaTrackDescriptor descriptor, long relatedSequence) {
        if (!registered || mediaSessionEpoch == 0 || descriptor.getTrackId() == 0
                || descriptor.getKindValue() == 0 || descriptor.getCodec().isEmpty()
                || descriptor.getPayloadFormat().isEmpty()
                || descriptor.getSourceTrackId().isEmpty()
                || descriptor.getCodecGeneration() == 0
                || descriptor.getCodecGenerationToken() == 0) {
            protocolViolation("媒体轨道描述不合法", relatedSequence);
            return;
        }
        tracks.put(descriptor.getTrackId(), descriptor);
        broker.publishNode(NodeEventType.NODE_EVENT_TYPE_MEDIA_CHANGED, snapshot(true));
        log.info("QUIC媒体轨道已登记，nodeId={}，trackId={}，kind={}，codec={}，format={}",
                nodeId, Integer.toUnsignedLong(descriptor.getTrackId()), descriptor.getKind(),
                descriptor.getCodec(), descriptor.getPayloadFormat());
    }

    public synchronized void processDatagram(ByteBuf datagram) {
        DatagramHeader header = DatagramHeader.decode(datagram);
        if (!registered || mediaSessionEpoch == 0 || header.getSessionEpoch() != mediaSessionEpoch) {
            throw invalidDatagram("媒体DATAGRAM不属于已注册会话");
        }
        MediaTrackDescriptor track = tracks.get((int) header.getTrackId());
        if (track == null || track.getKindValue() != header.getKind()
                || Integer.toUnsignedLong(track.getCodecGenerationToken())
                != header.getCodecGenerationToken()) {
            throw invalidDatagram("媒体DATAGRAM缺少匹配的轨道描述");
        }
        MediaReassembler.CompletedFrame complete =
                reassembler.accept(header, datagram, System.currentTimeMillis());
        if (complete == null) {
            return;
        }
        broker.publishMedia(MediaFrame.newBuilder()
                .setNodeId(nodeId)
                .setSessionId(sessionId)
                .setSessionEpoch(header.getSessionEpoch())
                .setTrackId((int) header.getTrackId())
                .setKind(header.getKind())
                .setCodec(track.getCodec())
                .setPayloadFormat(track.getPayloadFormat())
                .setPacketSequence(header.getPacketSequence())
                .setFrameSequence(header.getFrameSequence())
                .setCaptureTimestampUs(header.getCaptureTimestampUs())
                .setFlags(header.getFlags())
                .setPayload(ByteString.copyFrom(complete.getPayload()))
                .build());
    }

    public synchronized void expireReassembly() {
        long now = System.currentTimeMillis();
        long heartbeatAnchor = lastHeartbeatAtMs == 0 ? registeredAtMs : lastHeartbeatAtMs;
        if (registered && !offlinePublished && !heartbeatTimeoutClosing
                && now - heartbeatAnchor > config.getHeartbeatTimeoutMs()) {
            heartbeatTimeoutClosing = true;
            log.warn("QUIC终端业务心跳超时，主动关闭连接，nodeId={}，sessionId={}，timeoutMs={}",
                    nodeId, sessionId, config.getHeartbeatTimeoutMs());
            channel.close();
            return;
        }
        int expired = reassembler.expire(now);
        if (expired > 0) {
            log.debug("清理超时媒体帧，nodeId={}，expired={}，remaining={}",
                    nodeId, expired, reassembler.inFlightFrames());
        }
    }

    public synchronized void offline() {
        if (!registered || offlinePublished) return;
        offlinePublished = true;
        reassembler.clear();
        broker.publishNode(NodeEventType.NODE_EVENT_TYPE_OFFLINE, snapshot(false));
        log.info("QUIC终端离线，nodeId={}，sessionId={}，remoteIp={}",
                nodeId, sessionId, observedSourceIp);
    }

    public synchronized void fail(String message, Throwable error) {
        if (error == null) {
            log.warn("QUIC连接关闭，nodeId={}，原因={}", nodeId, message);
        } else {
            log.warn("QUIC连接异常，nodeId={}，原因={}", nodeId, message, error);
        }
        channel.close();
    }

    public synchronized void protocolViolation(String message, long relatedSequence) {
        log.warn("QUIC协议违规，nodeId={}，sessionId={}，relatedSequence={}，原因={}",
                nodeId, sessionId, relatedSequence, message);
        if (controlStream == null || !controlStream.isActive()) {
            channel.close();
            return;
        }
        EdgeControlEnvelope response = EdgeControlEnvelope.newBuilder()
                .setProtocolVersion(PROTOCOL_VERSION)
                .setMessageSequence(nextOutboundSequence())
                .setProtocolError(com.cmvr.quic.edge.v1.ProtocolError.newBuilder()
                        .setCode(PROTOCOL_ERROR_CODE)
                        .setMessage(message)
                        .setRelatedMessageSequence(relatedSequence)
                        .setFatal(true))
                .build();
        send(response).addListener(future -> channel.close());
    }

    private NodeSnapshot snapshot(boolean online) {
        NodeSnapshot.Builder builder = NodeSnapshot.newBuilder()
                .setNodeId(nodeId)
                .setBootId(bootId)
                .setSessionId(sessionId)
                .setSoftwareVersion(softwareVersion)
                .setOnline(online)
                .setRegisteredAtUnixMs(registeredAtMs)
                .setLastHeartbeatAtUnixMs(lastHeartbeatAtMs)
                .setHeartbeatSequence(heartbeatSequence)
                .setObservedSourceIp(observedSourceIp)
                .setMediaSessionEpoch(mediaSessionEpoch)
                .setGrpcEndpoint(com.cmvr.quic.gateway.v1.GrpcEndpoint.newBuilder()
                        .setHost(grpcEndpoint.getHost())
                        .setPort(grpcEndpoint.getPort())
                        .setTls(grpcEndpoint.getTls()));
        for (NetworkInterfaceAddress address : localInterfaces) {
            builder.addLocalInterfaces(com.cmvr.quic.gateway.v1.NetworkInterfaceAddress.newBuilder()
                    .setInterfaceName(address.getInterfaceName())
                    .setIpAddress(address.getIpAddress())
                    .setFamily(address.getFamilyValue())
                    .setLoopback(address.getLoopback()));
        }
        com.cmvr.quic.gateway.v1.DeviceManagerSnapshot.Builder manager =
                com.cmvr.quic.gateway.v1.DeviceManagerSnapshot.newBuilder()
                        .setManagerName(deviceManager.getManagerName())
                        .setManagerVersion(deviceManager.getManagerVersion())
                        .setManagerDescription(deviceManager.getManagerDescription())
                        .setSampledAtUnixMs(deviceManager.getSampledAtUnixMs());
        for (ManagedDeviceStatus device : deviceManager.getDevicesList()) {
            manager.addDevices(com.cmvr.quic.gateway.v1.ManagedDeviceStatus.newBuilder()
                    .setDeviceId(device.getDeviceId())
                    .setKind(device.getKindValue())
                    .setTypeName(device.getTypeName())
                    .setEnabled(device.getEnabled())
                    .setManagerState(device.getManagerStateValue())
                    .setHealth(device.getHealthValue())
                    .setHasError(device.getHasError())
                    .setErrorMessage(device.getErrorMessage())
                    .setStatusUpdatedAtUnixMs(device.getStatusUpdatedAtUnixMs()));
        }
        builder.setDeviceManager(manager);
        for (MediaTrackDescriptor descriptor : tracks.values()) {
            builder.addTracks(toMediaTrack(descriptor));
        }
        return builder.build();
    }

    private static MediaTrack toMediaTrack(MediaTrackDescriptor descriptor) {
        return MediaTrack.newBuilder()
                .setTrackId(descriptor.getTrackId())
                .setKind(descriptor.getKindValue())
                .setDeviceId(descriptor.getDeviceId())
                .setSourceTrackId(descriptor.getSourceTrackId())
                .setCodec(descriptor.getCodec())
                .setPayloadFormat(descriptor.getPayloadFormat())
                .setCodecGeneration(descriptor.getCodecGeneration())
                .setCodecGenerationToken(descriptor.getCodecGenerationToken())
                .setWidth(descriptor.getWidth())
                .setHeight(descriptor.getHeight())
                .setFramesPerSecond(descriptor.getFramesPerSecond())
                .setSampleRate(descriptor.getSampleRate())
                .setChannels(descriptor.getChannels())
                .setCodecConfig(descriptor.getCodecConfig())
                .build();
    }

    private ChannelFuture send(EdgeControlEnvelope envelope) {
        byte[] payload = envelope.toByteArray();
        ByteBuf frame = controlStream.alloc().buffer(4 + payload.length);
        frame.writeInt(payload.length);
        frame.writeBytes(payload);
        return controlStream.writeAndFlush(frame);
    }

    private long nextOutboundSequence() {
        return outboundSequence++;
    }

    private IllegalArgumentException invalidDatagram(String message) {
        long count = invalidDatagrams.incrementAndGet();
        if (count == 1 || count % 100 == 0) {
            log.warn("丢弃无效媒体DATAGRAM，nodeId={}，count={}，原因={}", nodeId, count, message);
        }
        return new IllegalArgumentException(message);
    }

    private static String remoteIp(SocketAddress remote) {
        if (remote instanceof InetSocketAddress) {
            InetSocketAddress address = (InetSocketAddress) remote;
            return address.getAddress() == null ? address.getHostString()
                    : address.getAddress().getHostAddress();
        }
        return remote == null ? "" : remote.toString();
    }
}
