package com.iailab.module.data.channel.opcua.collector;

import com.alibaba.fastjson.JSONObject;
import com.iailab.module.data.channel.opcua.dto.ChannelOPCUADeviceDTO;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

/**
 * @author PanZhibao
 * @Description
 * @createTime 2023年05月12日 15:36:00
 */
@Slf4j
@Component
public class OpcUaUtils {

    private final String CONNECTION_TYPE_USER = "1";

    private final String SECURITY_POLICY_BASIC256 = "Basic256";

    private final String SECURITY_POLICY_BASIC128RSA15 = "Basic128Rsa15";

    private final String SECURITY_POLICY_BASIC256SHA256 = "Basic256Sha256";

    public synchronized OpcUaClient createClient(ChannelOPCUADeviceDTO configDto) throws Exception {
        OpcUaClient opcUaClient = null;
        try {
            log.info("OpcuaDTO" + JSONObject.toJSONString(configDto));

            // 加载秘钥
            // Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
            log.info("加载秘钥");
            Path securityTempDir = Paths.get("D:\\DLUT\\pfx");
            Files.createDirectories(securityTempDir);
            if (!Files.exists(securityTempDir)) {
                throw new Exception("没有创建安全目录: " + securityTempDir);
            }
            KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);

            // 安全策略
            log.info("安全策略");
            SecurityPolicy securityPolicy = getSecurityPolicy(configDto.getSecurityPolicy());

            // 验证方式
            log.info("验证方式");
            IdentityProvider identityProvider = getIdentityProvider(configDto);

            // opc.tcp://DESKTOP-D7CDEIF:53530/OPCUA/SimulationServer
            // String endpointUrl = "opc.tcp://127.0.0.1:53530";
            log.info("连接端点");
            List<EndpointDescription> endpoints = DiscoveryClient.getEndpoints(configDto.getEndpointUrl()).get();
            endpoints.forEach(item -> {
                log.info("EndpointUrl=" + item.getEndpointUrl());
            });
            EndpointDescription endpoint = endpoints.stream()
                    .filter(e -> e.getSecurityPolicyUri().equals(securityPolicy.getUri()))
                    .findFirst()
                    .orElseThrow(() -> new Exception("没有连接上端点"));

            EndpointDescription newEndpoint = updateEndpointUrl(endpoint, getHostFromEndPointUrl(configDto.getEndpointUrl()));
            log.info("NewEndpointUrl=" + newEndpoint.getEndpointUrl());

            OpcUaClientConfig config = OpcUaClientConfig.builder()
                    .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
                    .setApplicationUri("")
                    .setCertificate(loader.getClientCertificate())
                    .setKeyPair(loader.getClientKeyPair())
                    .setEndpoint(newEndpoint)
                    .setIdentityProvider(identityProvider)
                    .setRequestTimeout(uint(5000))
                    .build();

            opcUaClient = OpcUaClient.create(config);
            log.info("创建客户端完成");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("创建客户端失败" + e.getMessage());
            throw new Exception(e.getMessage());
        }
        return opcUaClient;
    }

    private String getHostFromEndPointUrl (String endPointUrl) {
        int indexStart = endPointUrl.lastIndexOf("//");
        int indexEnd = endPointUrl.lastIndexOf(":");
        String host = endPointUrl.substring(indexStart, indexEnd).replace("//","");
        log.info(host);
        return host;
    }

    private EndpointDescription updateEndpointUrl(EndpointDescription original, String hostname) throws URISyntaxException {

        URI uri = new URI(original.getEndpointUrl()).parseServerAuthority();

        String endpointUrl = String.format(
                "%s://%s:%s%s",
                uri.getScheme(),
                hostname,
                uri.getPort(),
                uri.getPath()
        );

        return new EndpointDescription(
                endpointUrl,
                original.getServer(),
                original.getServerCertificate(),
                original.getSecurityMode(),
                original.getSecurityPolicyUri(),
                original.getUserIdentityTokens(),
                original.getTransportProfileUri(),
                original.getSecurityLevel()
        );
    }

    private IdentityProvider getIdentityProvider(ChannelOPCUADeviceDTO configDto) {
        switch (configDto.getConnectionType()) {
            case CONNECTION_TYPE_USER:
                return new UsernameProvider(configDto.getUserName(), configDto.getPassword());
            default:
                return new AnonymousProvider();
        }
    }

    private SecurityPolicy getSecurityPolicy(String type) {
        switch (type) {
            case SECURITY_POLICY_BASIC256:
                return SecurityPolicy.Basic256;
            case SECURITY_POLICY_BASIC128RSA15:
                return SecurityPolicy.Basic128Rsa15;
            case SECURITY_POLICY_BASIC256SHA256:
                return SecurityPolicy.Basic256Sha256;
            default:
                return SecurityPolicy.None;
        }
    }

    /**
     * 遍历树形节点
     *
     * @param client OPC UA客户端
     * @param uaNode 节点
     * @throws Exception
     */
    private static void browseNode(OpcUaClient client, UaNode uaNode) throws Exception {
        List<? extends UaNode> nodes;
        if (uaNode == null) {
            nodes = client.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
        } else {
            nodes = client.getAddressSpace().browseNodes(uaNode);
        }
        for (UaNode nd : nodes) {
            //排除系统行性节点,这些系统性节点名称一般都是以"_"开头
            if (Objects.requireNonNull(nd.getBrowseName().getName()).contains("_")) {
                continue;
            }
            System.out.println("Node= " + nd.getBrowseName().getName());
            browseNode(client, nd);
        }
    }

    /**
     * 读取节点数据
     * namespaceIndex可以通过UaExpert客户端去查询,一般来说这个值是2。
     * identifier也可以通过UaExpert客户端去查询,这个值=通道名称.设备名称.标记名称
     *
     * @param client    OPC UA客户端
     * @param nodeIdStr 标识符
     * @throws Exception
     */
    public static String readNode(OpcUaClient client, String nodeIdStr) throws Exception {
        String[] nodeIdArr = nodeIdStr.split(";");

        int namespaceIndex = Integer.parseInt(nodeIdArr[0].split("=")[1]);
        String identifier = nodeIdArr[1].split("=")[1];
        //节点
        NodeId nodeId = new NodeId(namespaceIndex, identifier);
        //读取节点数据
        DataValue value = client.readValue(0.0, TimestampsToReturn.Neither, nodeId).get();
        identifier = String.valueOf(nodeId.getIdentifier());
        System.out.println(identifier + ": " + String.valueOf(value.getValue().getValue()));
        return value.getValue().getValue().toString();
    }

    /**
     * 写入节点数据
     *
     * @param client
     * @param nodeIdStr
     * @param nodeValue
     */
    public static void writeIntValue(OpcUaClient client, String nodeIdStr, Integer nodeValue) {
        String[] nodeIdArr = nodeIdStr.split(";");

        int namespaceIndex = Integer.parseInt(nodeIdArr[0].split("=")[1]);
        String identifier = nodeIdArr[1].split("=")[1];
        NodeId nodeId = new NodeId(namespaceIndex, identifier);
        DataValue newValue = new DataValue(new Variant(nodeValue), null, null);
        //写入节点数据
        StatusCode statusCode = client.writeValue(nodeId, newValue).join();
        System.out.println("结果:" + statusCode.isGood());
    }

    /**
     * 写入节点数据
     *
     * @param client
     * @param nodeIdStr
     * @param nodeValue
     */
    public static void writeFloatValue(OpcUaClient client, String nodeIdStr, Float nodeValue) {
        String[] nodeIdArr = nodeIdStr.split(";");

        int namespaceIndex = Integer.parseInt(nodeIdArr[0].split("=")[1]);
        String identifier = nodeIdArr[1].split("=")[1];
        NodeId nodeId = new NodeId(namespaceIndex, identifier);
        DataValue newValue = new DataValue(new Variant(nodeValue), null, null);
        //写入节点数据
        StatusCode statusCode = client.writeValue(nodeId, newValue).join();
        System.out.println("结果:" + statusCode.isGood());
    }

    /**
     * 写入节点数据
     *
     * @param client
     * @param nodeIdStr
     * @param nodeValue
     */
    public static void writeBooleanValue(OpcUaClient client, String nodeIdStr, Boolean nodeValue) {
        String[] nodeIdArr = nodeIdStr.split(";");

        int namespaceIndex = Integer.parseInt(nodeIdArr[0].split("=")[1]);
        String identifier = nodeIdArr[1].split("=")[1];
        NodeId nodeId = new NodeId(namespaceIndex, identifier);
        DataValue newValue = new DataValue(new Variant(nodeValue), null, null);
        //写入节点数据
        StatusCode statusCode = client.writeValue(nodeId, newValue).join();
        System.out.println("结果:" + statusCode.isGood());
    }
}