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

import com.usky.cdi.service.sip.model.SdpMediaInfo;
import com.usky.cdi.service.sip.sdp.SdpUtils;
import com.usky.cdi.service.sip.util.NetworkUtils;
import gov.nist.javax.sip.SipStackImpl;
import lombok.extern.slf4j.Slf4j;

import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * SIP 客户端：向服务端发送 INVITE 并推送 H264 RTP 测试流
 */
@Slf4j
public class SipVideoPusher implements SipListener {

    private static final int PAYLOAD_TYPE = 96;
    private static final long SSRC = 0x12345678L;

    private final String serverHost;
    private final int serverPort;
    private final String deviceId;
    private final String platformId;
    private final int durationSeconds;
    private final String forcedLocalIp;

    private SipStack sipStack;
    private SipProvider sipProvider;
    private AddressFactory addressFactory;
    private MessageFactory messageFactory;
    private HeaderFactory headerFactory;

    private final AtomicReference<String> remoteRtpHost = new AtomicReference<>();
    private final AtomicReference<Integer> remoteRtpPort = new AtomicReference<>();
    private final AtomicReference<Dialog> dialogRef = new AtomicReference<>();
    private final AtomicReference<Long> inviteOkCseq = new AtomicReference<>();
    private final CountDownLatch inviteLatch = new CountDownLatch(1);
    private final List<String> errors = new ArrayList<>();

    private int localRtpPort;
    private String localIp;
    private String callId;
    private int cseq = 1;
    private long packetsSent;

    public SipVideoPusher(String serverHost, int serverPort, String deviceId,
                          String platformId, int durationSeconds) {
        this(serverHost, serverPort, deviceId, platformId, durationSeconds, null);
    }

    public SipVideoPusher(String serverHost, int serverPort, String deviceId,
                          String platformId, int durationSeconds, String forcedLocalIp) {
        this.serverHost = serverHost;
        this.serverPort = serverPort;
        this.deviceId = deviceId;
        this.platformId = platformId;
        this.durationSeconds = durationSeconds;
        this.forcedLocalIp = forcedLocalIp;
    }

    public PushResult push() {
        try {
            initStack();
            localIp = forcedLocalIp != null ? forcedLocalIp : NetworkUtils.resolveLocalIp(null);
            localRtpPort = findAvailablePort();
            sendInvite();
            if (!inviteLatch.await(10, TimeUnit.SECONDS)) {
                errors.add("等待 INVITE 响应超时");
                return buildResult(false);
            }
            if (remoteRtpPort.get() == null) {
                errors.add("未从 SDP 应答中解析到服务端 RTP 端口");
                return buildResult(false);
            }
            sendAck();
            packetsSent = sendRtpStream(durationSeconds);
            sendBye();
            return buildResult(true);
        } catch (Exception e) {
            log.error("推流测试失败: {}", e.getMessage(), e);
            errors.add(e.getMessage());
            return buildResult(false);
        } finally {
            shutdown();
        }
    }

    private void initStack() throws Exception {
        SipFactory sipFactory = SipFactory.getInstance();
        sipFactory.setPathName("gov.nist");
        java.util.Properties props = new java.util.Properties();
        props.setProperty("javax.sip.STACK_NAME", "UskySipVideoPusher");
        props.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "0");
        sipStack = sipFactory.createSipStack(props);
        addressFactory = sipFactory.createAddressFactory();
        messageFactory = sipFactory.createMessageFactory();
        headerFactory = sipFactory.createHeaderFactory();

        localIp = forcedLocalIp != null ? forcedLocalIp : NetworkUtils.resolveLocalIp(null);
        int sipLocalPort = findAvailablePort();
        ListeningPoint lp = sipStack.createListeningPoint(localIp, sipLocalPort, "udp");
        sipProvider = sipStack.createSipProvider(lp);
        sipProvider.addSipListener(this);
    }

    private void sendInvite() throws Exception {
        String sdp = SdpUtils.buildInviteSdp(localIp, localRtpPort, PAYLOAD_TYPE, deviceId);
        SipURI requestUri = addressFactory.createSipURI(platformId, serverHost);
        requestUri.setPort(serverPort);
        Address toAddress = addressFactory.createAddress(requestUri);
        ToHeader toHeader = headerFactory.createToHeader(toAddress, null);

        SipURI fromUri = addressFactory.createSipURI(deviceId, localIp);
        fromUri.setPort(sipProvider.getListeningPoint("udp").getPort());
        Address fromAddress = addressFactory.createAddress(fromUri);
        FromHeader fromHeader = headerFactory.createFromHeader(fromAddress, "push-tag");

        SipURI contactUri = addressFactory.createSipURI(deviceId, localIp);
        contactUri.setPort(sipProvider.getListeningPoint("udp").getPort());
        Address contactAddress = addressFactory.createAddress(contactUri);
        ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);

        List<ViaHeader> viaHeaders = new ArrayList<>();
        ViaHeader viaHeader = headerFactory.createViaHeader(localIp,
                sipProvider.getListeningPoint("udp").getPort(), "udp", null);
        viaHeaders.add(viaHeader);

        CallIdHeader callIdHeader = sipProvider.getNewCallId();
        callId = callIdHeader.getCallId();
        CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(cseq++, Request.INVITE);
        MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);
        ContentTypeHeader contentType = headerFactory.createContentTypeHeader("application", "sdp");

        Request invite = messageFactory.createRequest(requestUri, Request.INVITE, callIdHeader,
                cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards);
        invite.addHeader(contactHeader);
        invite.setContent(sdp, contentType);

        ClientTransaction ct = sipProvider.getNewClientTransaction(invite);
        ct.sendRequest();
        log.info("已发送 INVITE: {}:{} deviceId={} localRtpPort={}", serverHost, serverPort, deviceId, localRtpPort);
    }

    private void sendAck() throws Exception {
        Dialog dialog = dialogRef.get();
        if (dialog == null) {
            return;
        }
        long ackCseq = inviteOkCseq.get() != null ? inviteOkCseq.get() : 1L;
        Request ack = dialog.createAck(ackCseq);
        dialog.sendAck(ack);
        log.info("已发送 ACK");
    }

    private void sendBye() throws Exception {
        Dialog dialog = dialogRef.get();
        if (dialog == null) {
            return;
        }
        Request bye = dialog.createRequest(Request.BYE);
        CSeqHeader cSeqHeader = (CSeqHeader) bye.getHeader(CSeqHeader.NAME);
        cSeqHeader.setSeqNumber(cseq++);
        ClientTransaction ct = sipProvider.getNewClientTransaction(bye);
        dialog.sendRequest(ct);
        Thread.sleep(500);
        log.info("已发送 BYE");
    }

    private long sendRtpStream(int seconds) throws Exception {
        String host = remoteRtpHost.get();
        int port = remoteRtpPort.get();
        InetAddress address = InetAddress.getByName(host);
        DatagramSocket socket = new DatagramSocket();
        int seq = 0;
        long timestamp = 0;
        long count = 0;
        long end = System.currentTimeMillis() + seconds * 1000L;
        byte[] nalPayload = buildTestH264Nal();

        while (System.currentTimeMillis() < end) {
            byte[] rtp = buildRtpPacket(nalPayload, PAYLOAD_TYPE, seq++, timestamp, true);
            DatagramPacket packet = new DatagramPacket(rtp, rtp.length, address, port);
            socket.send(packet);
            count++;
            timestamp += 3000;
            Thread.sleep(33);
        }
        socket.close();
        log.info("RTP 推流完成: target={}:{}, packets={}", host, port, count);
        return count;
    }

    private byte[] buildTestH264Nal() {
        // 最小 SPS NAL (type 7)
        return new byte[]{(byte) 0x67, (byte) 0x42, (byte) 0x00, (byte) 0x0A, (byte) 0xF8, (byte) 0x3C};
    }

    private byte[] buildRtpPacket(byte[] payload, int pt, int seq, long timestamp, boolean marker) {
        byte[] packet = new byte[12 + payload.length];
        packet[0] = (byte) 0x80;
        packet[1] = (byte) ((marker ? 0x80 : 0) | (pt & 0x7F));
        packet[2] = (byte) (seq >> 8);
        packet[3] = (byte) (seq & 0xFF);
        packet[4] = (byte) (timestamp >> 24);
        packet[5] = (byte) (timestamp >> 16);
        packet[6] = (byte) (timestamp >> 8);
        packet[7] = (byte) (timestamp);
        packet[8] = (byte) (SSRC >> 24);
        packet[9] = (byte) (SSRC >> 16);
        packet[10] = (byte) (SSRC >> 8);
        packet[11] = (byte) (SSRC);
        System.arraycopy(payload, 0, packet, 12, payload.length);
        return packet;
    }

    private int findAvailablePort() throws Exception {
        try (DatagramSocket socket = new DatagramSocket(0)) {
            int port = socket.getLocalPort();
            return port % 2 == 0 ? port : port + 1;
        }
    }

    private void shutdown() {
        if (sipStack != null) {
            ((SipStackImpl) sipStack).stop();
        }
    }

    private PushResult buildResult(boolean success) {
        PushResult result = new PushResult();
        result.setSuccess(success);
        result.setCallId(callId);
        result.setLocalRtpPort(localRtpPort);
        result.setRemoteRtpHost(remoteRtpHost.get());
        result.setRemoteRtpPort(remoteRtpPort.get());
        result.setPacketsSent(packetsSent);
        result.setErrors(errors);
        return result;
    }

    @Override
    public void processResponse(ResponseEvent responseEvent) {
        Response response = responseEvent.getResponse();
        int status = response.getStatusCode();
        log.info("收到 SIP 响应: {}", status);
        if (status == Response.TRYING || status == Response.RINGING) {
            return;
        }
        if (status == Response.OK && responseEvent.getClientTransaction() != null
                && Request.INVITE.equals(responseEvent.getClientTransaction().getRequest().getMethod())) {
            try {
                Dialog dialog = responseEvent.getDialog();
                dialogRef.set(dialog);
                CSeqHeader cSeqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
                if (cSeqHeader != null) {
                    inviteOkCseq.set(cSeqHeader.getSeqNumber());
                }
                byte[] raw = response.getRawContent();
                if (raw != null) {
                    String sdp = new String(raw, StandardCharsets.UTF_8);
                    SdpMediaInfo media = SdpUtils.parseVideoMedia(sdp);
                    if (media != null) {
                        String host = media.getConnectionAddress();
                        if (host == null) {
                            host = serverHost;
                        }
                        remoteRtpHost.set(host);
                        remoteRtpPort.set(media.getPort());
                        log.info("解析 SDP 应答: rtp={}:{}", host, media.getPort());
                    }
                }
                inviteLatch.countDown();
            } catch (Exception e) {
                errors.add("处理 200 OK 失败: " + e.getMessage());
                inviteLatch.countDown();
            }
        } else if (status >= 300) {
            errors.add("INVITE 失败，状态码: " + status);
            inviteLatch.countDown();
        }
    }

    @Override
    public void processRequest(RequestEvent requestEvent) {
    }

    @Override
    public void processTimeout(TimeoutEvent timeoutEvent) {
        errors.add("SIP 超时");
        inviteLatch.countDown();
    }

    @Override
    public void processIOException(IOExceptionEvent exceptionEvent) {
        errors.add("SIP IO 异常");
    }

    @Override
    public void processTransactionTerminated(TransactionTerminatedEvent event) {
    }

    @Override
    public void processDialogTerminated(DialogTerminatedEvent event) {
    }

    @lombok.Data
    public static class PushResult {
        private boolean success;
        private String callId;
        private int localRtpPort;
        private String remoteRtpHost;
        private Integer remoteRtpPort;
        private long packetsSent;
        private List<String> errors = new ArrayList<>();
    }
}
