#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
VWED OPC UA模块
提供OPC UA通信相关的内置函数
"""

import threading
import time
from typing import Any, Dict, Optional
from utils.logger import get_logger
from opcua import Client, ua
from opcua.common.node import Node

logger = get_logger("services.opc_ua_module")


class VWEDOpcUaModule:
    """VWED OPC UA模块"""
    
    def __init__(self, script_id: str):
        self.script_id = script_id
        self._clients: Dict[str, Client] = {}  # 存储OPC UA客户端实例
        self._subscriptions: Dict[str, Any] = {}  # 存储订阅
        self._subscription_values: Dict[str, Any] = {}  # 存储订阅的值
        self._lock = threading.Lock()  # 线程锁
        logger.debug(f"初始化 VWEDOpcUaModule, script_id: {script_id}")

    def _get_or_create_client(self, endpoint: str) -> Client:
        """获取或创建OPC UA客户端"""
        with self._lock:
            if endpoint not in self._clients:
                client = Client(endpoint)
                self._clients[endpoint] = client
                logger.debug(f"创建OPC UA客户端: {endpoint}")
            return self._clients[endpoint]
    
    def _parse_node_id(self, namespace_index: int, address: str) -> ua.NodeId:
        """解析节点ID"""
        if address.startswith('i='):
            # 数字类型节点ID
            node_id = int(address[2:])
            return ua.NodeId(node_id, namespace_index)
        elif address.startswith('s='):
            # 字符串类型节点ID
            node_id = address[2:]
            return ua.NodeId(node_id, namespace_index)
        elif address.startswith('g='):
            # GUID类型节点ID
            node_id = address[2:]
            return ua.NodeId(node_id, namespace_index)
        elif address.startswith('b='):
            # 字节类型节点ID
            import base64
            node_id = base64.b64decode(address[2:])
            return ua.NodeId(node_id, namespace_index)
        else:
            # 默认当作字符串处理
            return ua.NodeId(address, namespace_index)
    
    def _connect_client(self, client: Client) -> bool:
        """连接客户端"""
        try:
            # 检查客户端是否已连接，尝试直接连接
            client.connect()
            logger.debug(f"连接OPC UA服务器成功")
            return True
        except Exception as e:
            # 如果连接失败，检查是否已经连接
            try:
                # 尝试一个简单的操作来验证连接
                client.get_root_node().get_browse_name()
                logger.debug(f"OPC UA客户端已连接")
                return True
            except:
                logger.error(f"连接OPC UA服务器失败: {e}")
                return False
    
    def _disconnect_client(self, client: Client):
        """断开客户端连接"""
        try:
            # 尝试断开连接，不检查内部状态
            client.disconnect()
            logger.debug(f"断开OPC UA服务器连接")
        except Exception as e:
            # 忽略断开连接时的错误，因为可能已经断开
            logger.debug(f"断开OPC UA服务器连接: {e}")
    
    def read_opc_value(self, address: str, endpoint: str = "opc.tcp://localhost:4840") -> Any:
        """
        OPC 读取数值 (简化版本)
        
        Args:
            address: 读取的地址位
            endpoint: OPC UA服务器地址
            
        Returns:
            Any: 读到的值
            
        Raises:
            RuntimeError: 读取失败时抛出异常
        """

        try:
            client = self._get_or_create_client(endpoint)
            if not self._connect_client(client):
                raise RuntimeError("read_opc_value error")
            
            # 使用默认命名空间0
            node_id = ua.NodeId(address, 0)
            node = client.get_node(node_id)
            value = node.get_value()
            
            logger.debug(f"读取OPC值成功: Address={address}, Value={value}")
            return value
            
        except Exception as e:
            logger.error(f"读取OPC值失败: {e}")
            raise RuntimeError("read_opc_value error")
    
    def read_opc_value_with_namespace(self, namespace_index: int, address: str, 
                                    endpoint: str = "opc.tcp://localhost:4840") -> Any:
        """
        OPC 读取数值 (带命名空间)
        
        Args:
            namespace_index: 命名空间索引
            address: 读取的地址位
            endpoint: OPC UA服务器地址
            
        Returns:
            Any: 读到的值
            
        Raises:
            RuntimeError: 读取失败时抛出异常
        """

        try:
            client = self._get_or_create_client(endpoint)
            if not self._connect_client(client):
                raise RuntimeError("read_opc_value error")
            
            node_id = self._parse_node_id(namespace_index, address)
            node = client.get_node(node_id)
            value = node.get_value()
            
            logger.debug(f"读取OPC值成功: NamespaceIndex={namespace_index}, Address={address}, Value={value}")
            return value
            
        except Exception as e:
            logger.error(f"读取OPC值失败: {e}")
            raise RuntimeError("read_opc_value error")
    
    def read_opc_value_by_subscription(self, namespace_index: int, address: str,
                                     endpoint: str = "opc.tcp://localhost:4840") -> Any:
        """
        OPC 订阅方式读取数值
        
        Args:
            namespace_index: 命名空间索引
            address: 读取的地址位
            endpoint: OPC UA服务器地址
            
        Returns:
            Any: 读到的值
            
        Raises:
            RuntimeError: 读取失败时抛出异常
        """

        subscription_key = f"{endpoint}_{namespace_index}_{address}"
        
        # 如果已经有订阅的值，直接返回
        if subscription_key in self._subscription_values:
            value = self._subscription_values[subscription_key]
            logger.debug(f"通过订阅读取OPC值: NamespaceIndex={namespace_index}, Address={address}, Value={value}")
            return value
        
        # 创建新的订阅
        client = None
        try:
            client = self._get_or_create_client(endpoint)
            if not self._connect_client(client):
                raise RuntimeError("read_opc_value error")
            
            # 创建订阅
            subscription = client.create_subscription(500, self._subscription_handler)
            node_id = self._parse_node_id(namespace_index, address)
            node = client.get_node(node_id)
            
            # 订阅节点变化
            handle = subscription.subscribe_data_change(node)
            
            # 存储订阅信息
            self._subscriptions[subscription_key] = {
                'subscription': subscription,
                'handle': handle,
                'client': client,
                'node': node
            }
            
            # 先读取一次当前值
            initial_value = node.get_value()
            self._subscription_values[subscription_key] = initial_value
            
            logger.debug(f"创建OPC订阅成功: NamespaceIndex={namespace_index}, Address={address}, InitialValue={initial_value}")
            return initial_value
            
        except Exception as e:
            logger.error(f"订阅方式读取OPC值失败: {e}")
            if client:
                self._disconnect_client(client)
            raise RuntimeError("read_opc_value error")
    
    def _subscription_handler(self, node: Node, val: Any, data):
        """订阅变化处理器"""
        try:
            # 找到对应的订阅键
            for key, sub_info in self._subscriptions.items():
                if sub_info['node'] == node:
                    self._subscription_values[key] = val
                    logger.debug(f"订阅值更新: Key={key}, Value={val}")
                    break
        except Exception as e:
            logger.error(f"处理订阅变化失败: {e}")
    
    def write_opc_value(self, address: str, value: Any, 
                       endpoint: str = "opc.tcp://localhost:4840") -> bool:
        """
        OPC 写入数值 (简化版本)
        
        Args:
            address: 写入的地址
            value: 写入的值
            endpoint: OPC UA服务器地址
            
        Returns:
            bool: 成功返回True，失败返回False
        """

        try:
            client = self._get_or_create_client(endpoint)
            if not self._connect_client(client):
                return False
            
            # 使用默认命名空间0
            node_id = ua.NodeId(address, 0)
            node = client.get_node(node_id)
            node.set_value(value)
            
            logger.debug(f"写入OPC值成功: Address={address}, Value={value}")
            return True
            
        except Exception as e:
            logger.error(f"写入OPC值失败: {e}")
            return False
    
    def write_opc_value_with_namespace(self, namespace_index: int, address: str, value: Any,
                                     endpoint: str = "opc.tcp://localhost:4840") -> bool:
        """
        OPC 写入数值 (带命名空间)
        
        Args:
            namespace_index: 命名空间索引
            address: 写入的地址
            value: 写入的值
            endpoint: OPC UA服务器地址
            
        Returns:
            bool: 成功返回True，失败返回False
        """

        try:
            client = self._get_or_create_client(endpoint)
            if not self._connect_client(client):
                return False
            
            node_id = self._parse_node_id(namespace_index, address)
            node = client.get_node(node_id)
            node.set_value(value)
            
            logger.debug(f"写入OPC值成功: NamespaceIndex={namespace_index}, Address={address}, Value={value}")
            return True
            
        except Exception as e:
            logger.error(f"写入OPC值失败: {e}")
            return False
    
    def cleanup(self):
        """清理资源"""
        try:
            # 清理所有订阅
            for key, sub_info in self._subscriptions.items():
                try:
                    subscription = sub_info['subscription']
                    if subscription:
                        subscription.delete()
                except Exception as e:
                    logger.error(f"清理订阅失败: {e}")
            
            # 断开所有客户端连接
            for endpoint, client in self._clients.items():
                try:
                    self._disconnect_client(client)
                except Exception as e:
                    logger.error(f"断开客户端连接失败: {e}")
            
            # 清空缓存
            self._subscriptions.clear()
            self._subscription_values.clear()
            self._clients.clear()
            
            logger.debug("OPC UA模块资源清理完成")
            
        except Exception as e:
            logger.error(f"清理OPC UA模块资源失败: {e}")