package com.usky.sas.common.onvif;

import com.alibaba.fastjson2.JSON;
import com.usky.sas.common.exception.BusinessException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

@Service("onvifStandardOnvifService")
public class StandardOnvifService {
    private static final Logger log = LoggerFactory.getLogger(StandardOnvifService.class);

    public StandardOnvifService() {
    }

    public String getRealTimeStreamUrl(String cameraIp, String username, String password) {
        try {
            String mediaServiceUrl = this.getServiceEndpoint(cameraIp, "http://www.onvif.org/ver10/media/wsdl");
            log.info("媒体服务端点: {}", mediaServiceUrl);
            String profileToken = this.getProfileToken(mediaServiceUrl, username, password);
            log.info("配置文件Token: {}", profileToken);
            String streamUri = this.getStreamUri(mediaServiceUrl, profileToken, username, password);
            log.info("获取到实时流地址: {}", streamUri);
            return streamUri;
        } catch (Exception e) {
            log.error("获取实时流地址失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    public String getServiceEndpoint(String deviceIp, String serviceNamespace) {
        try {
            String getServicesRequest = this.buildGetServicesRequest();
            String response = this.sendSoapRequest("http://" + deviceIp + "/onvif/device_service", getServicesRequest);
            Document doc = this.parseXml(response);
            NodeList serviceNodes = doc.getElementsByTagNameNS("http://www.onvif.org/ver10/schema", "Media");
            if (serviceNodes.getLength() == 0) {
                log.error("未找到 tt:Media 节点");
                throw new BusinessException("播放失败", -1);
            } else {
                Element mediaElement = (Element)serviceNodes.item(0);
                NodeList xaddrNodes = mediaElement.getElementsByTagNameNS("http://www.onvif.org/ver10/schema", "XAddr");
                if (xaddrNodes.getLength() > 0) {
                    return xaddrNodes.item(0).getTextContent();
                } else {
                    log.error("未找到 Media 的 XAddr");
                    throw new BusinessException("播放失败", -1);
                }
            }
        } catch (Exception e) {
            log.error("获取服务端点失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    private String buildGetServicesRequest() throws SOAPException {
        MessageFactory messageFactory = MessageFactory.newInstance();
        SOAPMessage soapMessage = messageFactory.createMessage();
        SOAPPart soapPart = soapMessage.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        envelope.addNamespaceDeclaration("tds", "http://www.onvif.org/ver10/device/wsdl");
        SOAPBody soapBody = envelope.getBody();
        SOAPElement getServicesElement = soapBody.addChildElement("GetCapabilities", "tds");
        SOAPElement includeCapabilityElement = getServicesElement.addChildElement("Category", "tds");
        includeCapabilityElement.addTextNode("All");
        return soapMessageToString(soapMessage);
    }

    public String getProfileToken(String mediaServiceUrl, String username, String password) {
        try {
            String getProfilesRequest = buildGetProfilesRequest(username, password);
            String response = this.sendSoapRequest(mediaServiceUrl, getProfilesRequest);
            Document doc = this.parseXml(response);
            NodeList profileNodes = doc.getElementsByTagNameNS("http://www.onvif.org/ver10/media/wsdl", "Profiles");
            if (profileNodes.getLength() > 0) {
                Element profileElement = (Element)profileNodes.item(0);
                return profileElement.getAttribute("token");
            } else {
                log.error("获取媒体配置文件失败");
                throw new BusinessException("播放失败", -1);
            }
        } catch (Exception e) {
            log.error("获取媒体配置文件失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    private static String buildGetProfilesRequest(String username, String password) throws Exception {
        MessageFactory messageFactory = MessageFactory.newInstance("SOAP 1.2 Protocol");
        SOAPMessage soapMessage = messageFactory.createMessage();
        SOAPPart soapPart = soapMessage.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        envelope.setPrefix("soap");
        SOAPHeader defaultHeader = envelope.getHeader();
        if (defaultHeader != null) {
            defaultHeader.detachNode();
        }

        SOAPHeader newHeader = envelope.addHeader();
        SOAPElement securityElement = newHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        securityElement.setAttribute("mustUnderstand", "1");
        SOAPElement usernameTokenElement = securityElement.addChildElement("UsernameToken", "wsse");
        SOAPElement usernameElement = usernameTokenElement.addChildElement("Username", "wsse");
        usernameElement.addTextNode(username);
        SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", "wsse");
        String nonce = generateNonce();
        nonceElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
        nonceElement.addTextNode(Base64.getEncoder().encodeToString(nonce.getBytes()));
        String created = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
        SOAPElement createdElement = usernameTokenElement.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        createdElement.addTextNode(created);
        String passwordDigest = calculatePasswordDigest(nonce, created, password);
        SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", "wsse");
        passwordElement.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
        passwordElement.addTextNode(passwordDigest);
        envelope.addNamespaceDeclaration("trt", "http://www.onvif.org/ver10/media/wsdl");
        SOAPBody soapBody = envelope.getBody();
        soapBody.setPrefix("soap");
        soapBody.addChildElement("GetProfiles", "trt");
        return soapMessageToString(soapMessage);
    }

    public String getStreamUri(String mediaServiceUrl, String profileToken, String username, String password) {
        try {
            String getStreamUriRequest = buildGetStreamUriRequest(profileToken, username, password);
            String response = this.sendSoapRequest(mediaServiceUrl, getStreamUriRequest);
            Document doc = this.parseXml(response);
            NodeList uriNodes = doc.getElementsByTagNameNS("*", "Uri");
            if (uriNodes.getLength() > 0) {
                return uriNodes.item(0).getTextContent();
            } else {
                log.error("获取rtsp流地址失败");
                throw new BusinessException("播放失败", -1);
            }
        } catch (Exception e) {
            log.error("获取流地址失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    private static String buildGetStreamUriRequest(String profileToken, String username, String password) throws Exception {
        MessageFactory messageFactory = MessageFactory.newInstance("SOAP 1.2 Protocol");
        SOAPMessage soapMessage = messageFactory.createMessage();
        SOAPPart soapPart = soapMessage.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        SOAPHeader defaultHeader = envelope.getHeader();
        if (defaultHeader != null) {
            defaultHeader.detachNode();
        }

        SOAPHeader newHeader = envelope.addHeader();
        SOAPElement securityElement = newHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        securityElement.setAttribute("mustUnderstand", "1");
        SOAPElement usernameTokenElement = securityElement.addChildElement("UsernameToken", "wsse");
        SOAPElement usernameElement = usernameTokenElement.addChildElement("Username", "wsse");
        usernameElement.addTextNode(username);
        SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", "wsse");
        String nonce = generateNonce();
        nonceElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
        nonceElement.addTextNode(Base64.getEncoder().encodeToString(nonce.getBytes()));
        String created = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
        SOAPElement createdElement = usernameTokenElement.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        createdElement.addTextNode(created);
        String passwordDigest = calculatePasswordDigest(nonce, created, password);
        SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", "wsse");
        passwordElement.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
        passwordElement.addTextNode(passwordDigest);
        envelope.addNamespaceDeclaration("trt", "http://www.onvif.org/ver10/media/wsdl");
        envelope.addNamespaceDeclaration("tt", "http://www.onvif.org/ver10/schema");
        SOAPBody soapBody = envelope.getBody();
        SOAPElement getStreamUriElement = soapBody.addChildElement("GetStreamUri", "trt");
        SOAPElement streamSetupElement = getStreamUriElement.addChildElement("StreamSetup", "trt");
        SOAPElement streamElement = streamSetupElement.addChildElement("Stream", "tt");
        streamElement.addTextNode("RTP-Unicast");
        SOAPElement transportElement = streamSetupElement.addChildElement("Transport", "tt");
        SOAPElement protocolElement = transportElement.addChildElement("Protocol", "tt");
        protocolElement.addTextNode("RTSP");
        SOAPElement profileTokenElement = getStreamUriElement.addChildElement("ProfileToken", "trt");
        profileTokenElement.addTextNode(profileToken);
        return soapMessageToString(soapMessage);
    }

    public String getReplayStreamUrl(String cameraIp, String username, String password) {
        try {
            String recordingServiceUrl = this.getServiceEndpoint(cameraIp, "http://www.onvif.org/ver10/recording/wsdl");
            log.info("回放录制配置服务端点: {}", recordingServiceUrl);
            List<RecordingInfo> recordingToken = this.getRecordingToken(recordingServiceUrl, username, password);
            log.info("录制配置Token: {}", JSON.toJSONString(recordingToken));
            String replayServiceUrl = this.getServiceEndpoint(cameraIp, "http://www.onvif.org/ver10/replay/wsdl");
            log.info("回放流服务端点: {}", recordingServiceUrl);
            String streamUri = this.getPlayStreamUri(replayServiceUrl, ((RecordingInfo)recordingToken.get(0)).getRecordingToken(), username, password);
            log.info("获取到回放流地址: {}", streamUri);
            return streamUri;
        } catch (Exception e) {
            log.error("获取回放流地址失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    public List<RecordingInfo> getRecordingToken(String recordingServiceUrl, String username, String password) {
        try {
            String getRecordingRequest = buildGetRecordingRequest(username, password);
            String response = this.sendSoapRequest(recordingServiceUrl, getRecordingRequest);
            return this.parseRecordingInfos(response);
        } catch (Exception e) {
            log.error("获取媒体配置文件失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    private static String buildGetRecordingRequest(String username, String password) throws Exception {
        MessageFactory messageFactory = MessageFactory.newInstance("SOAP 1.2 Protocol");
        SOAPMessage soapMessage = messageFactory.createMessage();
        SOAPPart soapPart = soapMessage.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        envelope.setPrefix("soap");
        SOAPHeader defaultHeader = envelope.getHeader();
        if (defaultHeader != null) {
            defaultHeader.detachNode();
        }

        SOAPHeader newHeader = envelope.addHeader();
        SOAPElement securityElement = newHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        securityElement.setAttribute("mustUnderstand", "1");
        SOAPElement usernameTokenElement = securityElement.addChildElement("UsernameToken", "wsse");
        SOAPElement usernameElement = usernameTokenElement.addChildElement("Username", "wsse");
        usernameElement.addTextNode(username);
        SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", "wsse");
        String nonce = generateNonce();
        nonceElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
        nonceElement.addTextNode(Base64.getEncoder().encodeToString(nonce.getBytes()));
        String created = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
        SOAPElement createdElement = usernameTokenElement.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        createdElement.addTextNode(created);
        String passwordDigest = calculatePasswordDigest(nonce, created, password);
        SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", "wsse");
        passwordElement.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
        passwordElement.addTextNode(passwordDigest);
        envelope.addNamespaceDeclaration("trc", "http://www.onvif.org/ver10/recording/wsdl");
        SOAPBody soapBody = envelope.getBody();
        soapBody.setPrefix("soap");
        soapBody.addChildElement("GetRecordings", "trc");
        return soapMessageToString(soapMessage);
    }

    private List<RecordingInfo> parseRecordingInfos(String xmlResponse) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(new ByteArrayInputStream(xmlResponse.getBytes(StandardCharsets.UTF_8)));
        XPathFactory xPathFactory = XPathFactory.newInstance();
        XPath xpath = xPathFactory.newXPath();
        xpath.setNamespaceContext(new NamespaceContext() {
            public String getNamespaceURI(String prefix) {
                switch (prefix) {
                    case "soap":
                        return "http://www.w3.org/2003/05/soap-envelope";
                    case "trec":
                        return "http://www.onvif.org/ver10/recording/wsdl";
                    case "tt":
                        return "http://www.onvif.org/ver10/schema";
                    default:
                        return null;
                }
            }

            public String getPrefix(String namespaceURI) {
                return null;
            }

            public Iterator<String> getPrefixes(String namespaceURI) {
                return null;
            }
        });
        NodeList items = (NodeList)xpath.evaluate("//trec:RecordingItem", doc, XPathConstants.NODESET);
        List<RecordingInfo> list = new ArrayList();

        for(int i = 0; i < items.getLength(); ++i) {
            Node item = items.item(i);
            RecordingInfo info = new RecordingInfo();
            info.recordingToken = (String)xpath.evaluate("tt:RecordingToken", item, XPathConstants.STRING);
            info.sourceId = (String)xpath.evaluate(".//tt:Source/tt:SourceId", item, XPathConstants.STRING);
            info.cameraName = (String)xpath.evaluate(".//tt:Source/tt:Name", item, XPathConstants.STRING);
            if (info.recordingToken != null) {
                info.recordingToken = info.recordingToken.trim();
            }

            if (info.sourceId != null) {
                info.sourceId = info.sourceId.trim();
            }

            if (info.cameraName != null) {
                info.cameraName = info.cameraName.trim();
            }

            list.add(info);
        }

        return list;
    }

    public String getPlayStreamUri(String replayServiceUrl, String recordingToken, String username, String password) {
        try {
            String getStreamUriRequest = buildGetPlayStreamUriRequest(recordingToken, username, password);
            String response = this.sendSoapRequest(replayServiceUrl, getStreamUriRequest);
            Document doc = this.parseXml(response);
            NodeList uriNodes = doc.getElementsByTagNameNS("*", "Uri");
            if (uriNodes.getLength() > 0) {
                return uriNodes.item(0).getTextContent();
            } else {
                log.error("获取回放流rtsp地址失败");
                throw new BusinessException("播放失败", -1);
            }
        } catch (Exception e) {
            log.error("获取回放流地址失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    private static String buildGetPlayStreamUriRequest(String recordingToken, String username, String password) throws Exception {
        MessageFactory messageFactory = MessageFactory.newInstance("SOAP 1.2 Protocol");
        SOAPMessage soapMessage = messageFactory.createMessage();
        SOAPPart soapPart = soapMessage.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        SOAPHeader defaultHeader = envelope.getHeader();
        if (defaultHeader != null) {
            defaultHeader.detachNode();
        }

        SOAPHeader newHeader = envelope.addHeader();
        SOAPElement securityElement = newHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        securityElement.setAttribute("mustUnderstand", "1");
        SOAPElement usernameTokenElement = securityElement.addChildElement("UsernameToken", "wsse");
        SOAPElement usernameElement = usernameTokenElement.addChildElement("Username", "wsse");
        usernameElement.addTextNode(username);
        SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", "wsse");
        String nonce = generateNonce();
        nonceElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
        nonceElement.addTextNode(Base64.getEncoder().encodeToString(nonce.getBytes()));
        String created = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
        SOAPElement createdElement = usernameTokenElement.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        createdElement.addTextNode(created);
        String passwordDigest = calculatePasswordDigest(nonce, created, password);
        SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", "wsse");
        passwordElement.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
        passwordElement.addTextNode(passwordDigest);
        envelope.addNamespaceDeclaration("trp", "http://www.onvif.org/ver10/replay/wsdl");
        envelope.addNamespaceDeclaration("tt", "http://www.onvif.org/ver10/schema");
        SOAPBody soapBody = envelope.getBody();
        SOAPElement getStreamUriElement = soapBody.addChildElement("GetReplayUri", "trp");
        SOAPElement streamSetupElement = getStreamUriElement.addChildElement("StreamSetup");
        SOAPElement streamElement = streamSetupElement.addChildElement("Stream");
        streamElement.addTextNode("RTP-Unicast");
        SOAPElement transportElement = streamSetupElement.addChildElement("Transport");
        SOAPElement protocolElement = transportElement.addChildElement("Protocol");
        protocolElement.addTextNode("RTSP");
        SOAPElement recordingTokenElement = getStreamUriElement.addChildElement("RecordingToken");
        recordingTokenElement.addTextNode(recordingToken);
        return soapMessageToString(soapMessage);
    }

    private String sendSoapRequest(String url, String soapRequest) {
        try {
            URL soapUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection)soapUrl.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
            connection.setRequestProperty("SOAPAction", "");

            try (OutputStream os = connection.getOutputStream()) {
                BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
                Throwable errorReader = null;

                try {
                    writer.write(soapRequest);
                    writer.flush();
                } catch (Throwable var160) {
                    errorReader = var160;
                    throw var160;
                } finally {
                    if (writer != null) {
                        if (errorReader != null) {
                            try {
                                writer.close();
                            } catch (Throwable var159) {
                                errorReader.addSuppressed(var159);
                            }
                        } else {
                            writer.close();
                        }
                    }

                }
            }

            int responseCode = connection.getResponseCode();
            if (responseCode == 200) {
                String var12;
                try (InputStream is = connection.getInputStream()) {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
                    Throwable var180 = null;

                    try {
                        StringBuilder responseBuilder = new StringBuilder();

                        String line;
                        while((line = reader.readLine()) != null) {
                            responseBuilder.append(line).append("\n");
                        }

                        var12 = responseBuilder.toString();
                    } catch (Throwable var164) {
                        var180 = var164;
                        throw var164;
                    } finally {
                        if (reader != null) {
                            if (var180 != null) {
                                try {
                                    reader.close();
                                } catch (Throwable var157) {
                                    var180.addSuppressed(var157);
                                }
                            } else {
                                reader.close();
                            }
                        }

                    }
                }

                return var12;
            } else {
                try (InputStream errorStream = connection.getErrorStream()) {
                    BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8));
                    Throwable var9 = null;

                    try {
                        StringBuilder errorBuilder = new StringBuilder();

                        String line;
                        while((line = errorReader.readLine()) != null) {
                            errorBuilder.append(line).append("\n");
                        }

                        log.error("发送SOAP请求失败");
                        throw new BusinessException("播放失败", -1);
                    } catch (Throwable var168) {
                        var9 = var168;
                        throw var168;
                    } finally {
                        if (errorReader != null) {
                            if (var9 != null) {
                                try {
                                    errorReader.close();
                                } catch (Throwable var155) {
                                    var9.addSuppressed(var155);
                                }
                            } else {
                                errorReader.close();
                            }
                        }

                    }
                }
            }
        } catch (Exception e) {
            log.error("发送 SOAP 请求失败: {}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    private static String soapMessageToString(SOAPMessage message) throws SOAPException {
        try {
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            transformer.setOutputProperty("indent", "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            StringWriter writer = new StringWriter();
            transformer.transform(new DOMSource(message.getSOAPPart()), new StreamResult(writer));
            return writer.toString();
        } catch (Exception e) {
            log.error("转换SOAP消息失败:{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    public List<String> getRealSources(String cameraIp, String username, String password) {
        try {
            String mediaServiceUrl = this.getServiceEndpoint(cameraIp, "http://www.onvif.org/ver10/media/wsdl");
            log.info("媒体服务端点: {}", mediaServiceUrl);
            List<String> sources = this.getSources(mediaServiceUrl, username, password);
            return sources;
        } catch (Exception e) {
            log.error("获取NVR设备通道数失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    public List<String> getSources(String mediaServiceUrl, String username, String password) {
        try {
            String getProfilesRequest = buildGetSourceRequest(username, password);
            String response = this.sendSoapRequest(mediaServiceUrl, getProfilesRequest);
            Document doc = this.parseXml(response);
            NodeList sources = doc.getElementsByTagNameNS("http://www.onvif.org/ver10/media/wsdl", "VideoSources");
            if (sources.getLength() <= 0) {
                log.error("获取设备source失败");
                throw new BusinessException("播放失败", -1);
            } else {
                List<String> videoSourceTokenList = new ArrayList();

                for(int i = 0; i < sources.getLength(); ++i) {
                    Element profileElement = (Element)sources.item(i);
                    String videoSourceToken = profileElement.getAttribute("token");
                    videoSourceTokenList.add(videoSourceToken);
                }

                return videoSourceTokenList;
            }
        } catch (Exception e) {
            log.error("获取设备source失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    public String getNvrRealTimeStreamUrl(String cameraIp, String username, String password, String videoSourceToken) {
        try {
            String mediaServiceUrl = this.getServiceEndpoint(cameraIp, "http://www.onvif.org/ver10/media/wsdl");
            log.info("媒体服务端点: {}", mediaServiceUrl);
            String profileToken = this.getNvrProfilesToken(mediaServiceUrl, username, password, videoSourceToken);
            log.info("配置文件Token: {}", profileToken);
            String streamUri = this.getStreamUri(mediaServiceUrl, profileToken, username, password);
            log.info("获取到实时流地址: {}", streamUri);
            return streamUri;
        } catch (Exception e) {
            log.error("获取实时流地址失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    public String getNvrProfilesToken(String mediaServiceUrl, String username, String password, String videoSourceToken) {
        try {
            String getProfilesRequest = buildGetProfilesRequest(username, password);
            String response = this.sendSoapRequest(mediaServiceUrl, getProfilesRequest);
            Document doc = this.parseXml(response);
            NodeList profileNodes = doc.getElementsByTagNameNS("http://www.onvif.org/ver10/media/wsdl", "Profiles");
            if (profileNodes.getLength() > 0) {
                for(int i = 0; i < profileNodes.getLength(); ++i) {
                    Element profileElement = (Element)profileNodes.item(i);
                    String profileToken = profileElement.getAttribute("token");
                    NodeList videoSourceConfigNodes = profileElement.getElementsByTagNameNS("http://www.onvif.org/ver10/schema", "VideoSourceConfiguration");
                    if (videoSourceConfigNodes.getLength() > 0) {
                        Element videoSourceConfig = (Element)videoSourceConfigNodes.item(0);
                        NodeList sourceTokenNodes = videoSourceConfig.getElementsByTagNameNS("http://www.onvif.org/ver10/schema", "SourceToken");
                        if (sourceTokenNodes.getLength() > 0) {
                            String sourceToken = sourceTokenNodes.item(0).getTextContent();
                            if (videoSourceToken.equals(sourceToken)) {
                                log.info("找到匹配的Profile: {} 对应 VideoSourceToken: {}", profileToken, videoSourceToken);
                                return profileToken;
                            }
                        }
                    }
                }
            }

            log.error("获取媒体配置文件失败");
            throw new BusinessException("播放失败", -1);
        } catch (Exception e) {
            log.error("获取媒体配置文件失败：{}", e.getMessage());
            throw new BusinessException("播放失败", -1);
        }
    }

    private static String buildGetSourceRequest(String username, String password) throws Exception {
        MessageFactory messageFactory = MessageFactory.newInstance("SOAP 1.2 Protocol");
        SOAPMessage soapMessage = messageFactory.createMessage();
        SOAPPart soapPart = soapMessage.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        SOAPHeader defaultHeader = envelope.getHeader();
        if (defaultHeader != null) {
            defaultHeader.detachNode();
        }

        SOAPHeader newHeader = envelope.addHeader();
        SOAPElement securityElement = newHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        securityElement.setAttribute("mustUnderstand", "1");
        SOAPElement usernameTokenElement = securityElement.addChildElement("UsernameToken", "wsse");
        SOAPElement usernameElement = usernameTokenElement.addChildElement("Username", "wsse");
        usernameElement.addTextNode(username);
        SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", "wsse");
        String nonce = generateNonce();
        nonceElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
        nonceElement.addTextNode(Base64.getEncoder().encodeToString(nonce.getBytes()));
        String created = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
        SOAPElement createdElement = usernameTokenElement.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        createdElement.addTextNode(created);
        String passwordDigest = calculatePasswordDigest(nonce, created, password);
        SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", "wsse");
        passwordElement.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
        passwordElement.addTextNode(passwordDigest);
        envelope.addNamespaceDeclaration("trt", "http://www.onvif.org/ver10/media/wsdl");
        SOAPBody soapBody = envelope.getBody();
        SOAPElement getStreamUriElement = soapBody.addChildElement("GetVideoSources", "trt");
        return soapMessageToString(soapMessage);
    }

    private Document parseXml(String xmlString) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        return builder.parse(new InputSource(new StringReader(xmlString)));
    }

    private static String generateNonce() {
        SecureRandom random = new SecureRandom();
        byte[] nonceBytes = new byte[16];
        random.nextBytes(nonceBytes);
        return new String(nonceBytes, StandardCharsets.UTF_8);
    }

    private static String calculatePasswordDigest(String nonce, String created, String password) throws Exception {
        String combined = nonce + created + password;
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        byte[] digest = md.digest(combined.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(digest);
    }
}
