package cn.iocoder.yudao.framework.common.util.opc;

import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
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.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;

import java.util.concurrent.*;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class OpcUtils {

    /**
     * OPC Client缓存池
     */
    private static final ConcurrentHashMap<Long, OpcUaClient> CLIENT_MAP =
            new ConcurrentHashMap<>();

    /**
     * 连接锁
     */
    private static final ConcurrentHashMap<Long, Object> LOCK_MAP =
            new ConcurrentHashMap<>();


    public static boolean connect(Long deviceId,
                                  String url,
                                  String username,
                                  String password,
                                  int timeoutSeconds) {

        try {

            OpcUaClient client = CLIENT_MAP.get(deviceId);

            if (isConnected(client)) {
                return true;
            }

            Object lock = LOCK_MAP.computeIfAbsent(deviceId, k -> new Object());

            synchronized (lock) {

                client = CLIENT_MAP.get(deviceId);
                if (isConnected(client)) {
                    return true;
                }

                log.info("创建OPC连接 deviceId={}, url={}", deviceId, url);

                // 只用用户名/密码连接
                IdentityProvider identityProvider = new UsernameProvider(username, password);

                OpcUaClient newClient = OpcUaClient.create(
                        url,
                        // 只选择 SecurityPolicy.None 的端点
                        endpoints -> endpoints.stream()
                                .filter(ep -> ep.getSecurityPolicyUri().equals("http://opcfoundation.org/UA/SecurityPolicy#None"))
                                .findFirst(),
                        configBuilder -> configBuilder
                                .setIdentityProvider(identityProvider)
                                .setRequestTimeout(uint(timeoutSeconds * 1000))
                                .build()
                );

                newClient.connect().get(timeoutSeconds, TimeUnit.SECONDS);

                CLIENT_MAP.put(deviceId, newClient);

                log.info("OPC连接成功 deviceId={}", deviceId);
                return true;
            }

        } catch (Exception e) {
            log.error("OPC连接失败 deviceId={}", deviceId, e);
            CLIENT_MAP.remove(deviceId);
            return false;
        }
    }


    /**
     * 批量读取（线程安全 + 自动检测session）
     */
    public static List<Object> readValues(Long deviceId,
                                          List<String> addresses) {

        OpcUaClient client = CLIENT_MAP.get(deviceId);

        if (!isConnected(client)) {

            log.warn("OPC未连接或session失效 deviceId={}", deviceId);

            CLIENT_MAP.remove(deviceId);

            return buildNullResult(addresses.size());
        }

        try {

            List<NodeId> nodeIds =
                    addresses.stream()
                            .map(OpcUtils::parseNodeIdSafe)
                            .collect(Collectors.toList());

            List<DataValue> values =
                    client.readValues(
                                    0.0,
                                    TimestampsToReturn.Both,
                                    nodeIds
                            )
                            .get(5, TimeUnit.SECONDS);

            List<Object> result =
                    new ArrayList<>(values.size());

            for (DataValue dv : values) {

                if (dv == null || dv.getValue() == null) {
                    result.add(null);
                    continue;
                }

                Variant variant = dv.getValue();

                result.add(
                        variant != null
                                ? variant.getValue()
                                : null
                );
            }

            return result;

        } catch (Exception e) {

            log.error("OPC读取失败 deviceId={}", deviceId, e);

            CLIENT_MAP.remove(deviceId);

            return buildNullResult(addresses.size());
        }
    }


    /**
     * 判断连接是否有效（关键方法）
     */
    private static boolean isConnected(OpcUaClient client) {

        if (client == null) {
            return false;
        }

        try {

            client.getSession()
                    .get(200, TimeUnit.MILLISECONDS);

            return true;

        } catch (Exception e) {

            return false;
        }
    }


    /**
     * 安全解析NodeId（支持全部格式）
     *
     * ns=2;s=Tag
     * ns=2;i=123
     * ns=3;g=UUID
     */
    private static NodeId parseNodeIdSafe(String address) {

        try {

            if (address == null ||
                    address.trim().isEmpty()) {
                return null;
            }

            return NodeId.parse(address.trim());

        } catch (Exception e) {

            log.warn("NodeId解析失败: {}", address);

            return null;
        }
    }


    /**
     * 构造null结果（防止任务崩溃）
     */
    private static List<Object> buildNullResult(int size) {

        List<Object> result =
                new ArrayList<>(size);

        for (int i = 0; i < size; i++) {
            result.add(null);
        }

        return result;
    }


    /**
     * 断开指定设备
     */
    public static boolean disconnect(Long deviceId) {

        try {

            OpcUaClient client =
                    CLIENT_MAP.remove(deviceId);

            if (client != null) {

                client.disconnect().get();

                log.info("OPC断开 deviceId={}", deviceId);
                return true;
            }

        } catch (Exception e) {

            log.error("断开失败 deviceId={}", deviceId, e);
            return false;

        }
        return true;

    }


    /**
     * 系统关闭时调用
     */
    public static void disconnectAll() {

        CLIENT_MAP.forEach((deviceId, client) -> {

            try {
                client.disconnect().get();
            } catch (Exception ignored) {}
        });

        CLIENT_MAP.clear();

        log.info("全部OPC连接关闭");
    }


    private static
    org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger
    uint(long value) {

        return org.eclipse.milo.opcua.stack.core.types.builtin.unsigned
                .Unsigned.uint(value);
    }
}
