package com.usky.cdi.service.sip.push;

import com.usky.cdi.service.sip.model.SdpMediaInfo;
import com.usky.cdi.service.sip.ps.Gb28181PsMuxer;
import com.usky.cdi.service.sip.ps.H264AccessUnitBuilder;
import com.usky.cdi.service.sip.ps.H264ExtradataUtils;
import com.usky.cdi.service.sip.rtp.AnnexBParser;
import com.usky.cdi.service.sip.rtp.PsRtpPacketizer;
import com.usky.cdi.service.sip.sdp.SdpUtils;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameGrabber;

import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

/**
 * GB/T 28181 推流：JavaCV 拉 RTSP → 海康兼容 PS 封装 → RTP(PT=96)。
 */
@Slf4j
public final class RtspGb28181RtpSender {

    private static final long DEFAULT_SSRC = 100_000_001L;
    private static final int DEFAULT_TIMESTAMP_INCREMENT = 3600;
    private static final long RTSP_DATA_WAIT_MS = 20_000L;
    private static final long FIRST_RTP_WAIT_MS = 45_000L;

    private RtspGb28181RtpSender() {
    }

    public static long stream(String rtspUrl, SdpMediaInfo media, String rtpHost, int rtpPort,
                              int localRtpPort, int durationSeconds, String rtspTransport) throws Exception {
        int payloadType = media != null && media.getPayloadType() > 0 ? media.getPayloadType() : 96;
        long ssrc = SdpUtils.parseSsrcValue(media != null ? media.getSsrc() : null, DEFAULT_SSRC);
        boolean useTcp = media != null && media.isTcpMedia();
        String answerSetup = media != null && "active".equalsIgnoreCase(media.getSetup()) ? "passive" : "active";

        log.info("GB28181 PS 推流: target={}:{}, localRtpPort={}, ssrc={}, tcp={}",
                rtpHost, rtpPort, localRtpPort, ssrc, useTcp);

        InetAddress remoteAddress = InetAddress.getByName(rtpHost);
        AtomicLong packetCount = new AtomicLong();
        H264AccessUnitBuilder auBuilder = new H264AccessUnitBuilder();

        try (RtpTransport transport = openTransport(useTcp, answerSetup, remoteAddress, rtpPort, localRtpPort)) {
            FFmpegFrameGrabber grabber = createGrabber(rtspUrl, rtspTransport);
            try {
                grabber.start();
                int videoStream = grabber.getVideoStream();
                double fps = grabber.getFrameRate();
                int timestampIncrement = fps > 1 && fps < 121
                        ? (int) (90000 / fps) : DEFAULT_TIMESTAMP_INCREMENT;
                H264ExtradataUtils.seedParameterSets(grabber, auBuilder);
                log.info("RTSP 已连接: videoStream={}, fps={}, tsInc={}, hasSpsPps={}",
                        videoStream, fps, timestampIncrement, auBuilder.hasParameterSets());

                int sequence = 0;
                long timestamp = 0;
                long endTime = System.currentTimeMillis() + durationSeconds * 1000L;
                long streamStart = System.currentTimeMillis();
                long rtspDataDeadline = streamStart + RTSP_DATA_WAIT_MS;
                long firstRtpDeadline = streamStart + FIRST_RTP_WAIT_MS;
                long rtspVideoPackets = 0;
                long psPacks = 0;

                while (System.currentTimeMillis() < endTime) {
                    AVPacket pkt = grabber.grabPacket();
                    if (pkt == null || pkt.size() <= 0) {
                        if (rtspVideoPackets == 0 && System.currentTimeMillis() > rtspDataDeadline) {
                            throw new IllegalStateException(
                                    "RTSP 在 " + (RTSP_DATA_WAIT_MS / 1000) + "s 内未收到视频包");
                        }
                        Thread.sleep(2);
                        continue;
                    }
                    if (pkt.stream_index() != videoStream) {
                        continue;
                    }
                    rtspVideoPackets++;

                    byte[] data = new byte[pkt.size()];
                    pkt.data().get(data, 0, pkt.size());
                    boolean keyFrame = (pkt.flags() & avcodec.AV_PKT_FLAG_KEY) != 0;
                    byte[] accessUnit = auBuilder.build(data, keyFrame);
                    if (accessUnit == null) {
                        if (packetCount.get() == 0 && System.currentTimeMillis() > firstRtpDeadline) {
                            throw new IllegalStateException(
                                    "等待首个可发送帧超时，已收 RTSP 包=" + rtspVideoPackets
                                            + "，hasSpsPps=" + auBuilder.hasParameterSets());
                        }
                        continue;
                    }

                    List<byte[]> nals = AnnexBParser.extractNals(accessUnit);
                    boolean psKeyFrame = keyFrame || auBuilder.isKeyFrame(nals);
                    byte[] psFrame = Gb28181PsMuxer.mux(nals, timestamp, psKeyFrame);
                    if (psFrame.length == 0) {
                        continue;
                    }
                    sequence = sendPsFrame(transport, psFrame, payloadType, sequence, timestamp, ssrc, packetCount);
                    timestamp += timestampIncrement;
                    psPacks++;

                    if (psPacks == 1) {
                        log.info("首包 PS 已发送: psBytes={}, keyFrame={}, pesHead=[{}], rtspPackets={}",
                                psFrame.length, psKeyFrame, hexHead(psFrame, 48), rtspVideoPackets);
                    }
                }

                log.info("GB28181 推流统计: psPacks={}, rtpPackets={}, rtspVideoPackets={}",
                        psPacks, packetCount.get(), rtspVideoPackets);
                if (packetCount.get() == 0) {
                    throw new IllegalStateException("推流结束但未发出 RTP");
                }
            } finally {
                closeGrabber(grabber);
            }
        }
        return packetCount.get();
    }

    private static int sendPsFrame(RtpTransport transport, byte[] psFrame, int payloadType,
                                   int sequence, long timestamp, long ssrc, AtomicLong packetCount) throws Exception {
        List<byte[]> rtpPackets = PsRtpPacketizer.packetize(psFrame, payloadType, sequence, timestamp, ssrc);
        for (byte[] rtp : rtpPackets) {
            transport.send(rtp);
            packetCount.incrementAndGet();
        }
        return (sequence + rtpPackets.size()) & 0xFFFF;
    }

    private static FFmpegFrameGrabber createGrabber(String rtspUrl, String rtspTransport) {
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(rtspUrl);
        grabber.setFormat("rtsp");
        grabber.setOption("rtsp_transport", rtspTransport != null ? rtspTransport : "tcp");
        grabber.setOption("stimeout", "5000000");
        grabber.setOption("max_delay", "500000");
        grabber.setOption("fflags", "nobuffer");
        grabber.setOption("flags", "low_delay");
        return grabber;
    }

    private static void closeGrabber(FFmpegFrameGrabber grabber) {
        try {
            grabber.stop();
            grabber.release();
        } catch (Exception e) {
            log.debug("关闭 RTSP: {}", e.getMessage());
        }
    }

    private static String hexHead(byte[] data, int max) {
        if (data == null || data.length == 0) {
            return "";
        }
        int n = Math.min(data.length, max);
        StringBuilder sb = new StringBuilder(n * 3);
        for (int i = 0; i < n; i++) {
            if (i > 0) {
                sb.append(' ');
            }
            sb.append(String.format("%02X", data[i] & 0xFF));
        }
        return sb.toString();
    }

    private static RtpTransport openTransport(boolean useTcp, String answerSetup, InetAddress remoteHost,
                                              int remotePort, int localPort) throws Exception {
        if (!useTcp) {
            return new UdpTransport(remoteHost, remotePort, localPort);
        }
        if ("active".equalsIgnoreCase(answerSetup)) {
            Socket socket = new Socket(remoteHost, remotePort);
            socket.setTcpNoDelay(true);
            return new TcpTransport(socket);
        }
        ServerSocket serverSocket = new ServerSocket(localPort);
        serverSocket.setSoTimeout(30000);
        Socket socket = serverSocket.accept();
        serverSocket.close();
        socket.setTcpNoDelay(true);
        return new TcpTransport(socket);
    }

    private interface RtpTransport extends AutoCloseable {
        void send(byte[] rtp) throws Exception;

        @Override
        void close();
    }

    private static final class UdpTransport implements RtpTransport {
        private final DatagramSocket socket;
        private final DatagramPacket template;

        UdpTransport(InetAddress host, int port, int localPort) throws Exception {
            if (localPort > 0) {
                this.socket = new DatagramSocket(localPort);
                log.info("RTP 绑定本地端口: {}", localPort);
            } else {
                this.socket = new DatagramSocket();
            }
            this.template = new DatagramPacket(new byte[0], 0, host, port);
        }

        @Override
        public void send(byte[] rtp) throws Exception {
            template.setData(rtp);
            template.setLength(rtp.length);
            socket.send(template);
        }

        @Override
        public void close() {
            socket.close();
        }
    }

    private static final class TcpTransport implements RtpTransport {
        private final Socket socket;
        private final OutputStream out;

        TcpTransport(Socket socket) throws Exception {
            this.socket = socket;
            this.out = socket.getOutputStream();
        }

        @Override
        public void send(byte[] rtp) throws Exception {
            byte[] frame = new byte[rtp.length + 2];
            frame[0] = (byte) ((rtp.length >> 8) & 0xFF);
            frame[1] = (byte) (rtp.length & 0xFF);
            System.arraycopy(rtp, 0, frame, 2, rtp.length);
            out.write(frame);
            out.flush();
        }

        @Override
        public void close() {
            try {
                out.close();
            } catch (Exception ignored) {
            }
            try {
                socket.close();
            } catch (Exception ignored) {
            }
        }
    }
}
