import pytest
import sys
import os
from unittest.mock import MagicMock, patch

# 添加项目根目录到Python路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from modules.mqtt_client import MQTTClient


class TestMQTTClient:
    
    def setup_method(self):
        """每个测试方法执行前的设置"""
        # 创建MQTTClient实例
        self.mqtt_client = MQTTClient()
        # 模拟数据处理和状态处理回调
        self.data_callback = MagicMock()
        self.status_callback = MagicMock()
        self.mqtt_client.set_data_callback(self.data_callback)
        self.mqtt_client.set_status_callback(self.status_callback)
    
    def teardown_method(self):
        """每个测试方法执行后的清理"""
        # 确保断开连接
        self.mqtt_client.disconnect()
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_connect_success(self, mock_client_class):
        """测试成功连接MQTT服务器"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0  # 0表示连接成功
        mock_client_class.return_value = mock_client
        
        # 调用connect方法
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        success, message = self.mqtt_client.connect(config)
        
        # 验证结果
        assert success is True
        assert message == "MQTT连接成功"
        mock_client.connect.assert_called_once_with(
            'localhost', 1883, 60
        )
        mock_client.loop_start.assert_called_once()
        # 验证状态回调被调用，且状态为True
        self.status_callback.assert_called_once_with(True)
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_connect_failure(self, mock_client_class):
        """测试连接MQTT服务器失败"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象，但connect返回错误码
        mock_client = MagicMock()
        mock_client.connect.return_value = 1  # 非0表示连接失败
        mock_client_class.return_value = mock_client
        
        # 调用connect方法
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        success, message = self.mqtt_client.connect(config)
        
        # 验证结果
        assert success is False
        assert "连接失败" in message
        mock_client.connect.assert_called_once()
        mock_client.loop_start.assert_not_called()
        # 验证状态回调被调用，且状态为False
        self.status_callback.assert_called_once_with(False)
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_connect_with_tls(self, mock_client_class):
        """测试使用TLS连接MQTT服务器"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0
        mock_client_class.return_value = mock_client
        
        # 调用connect方法，启用TLS
        config = {
            'broker': 'localhost',
            'port': 8883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': True,
            'ca_certs': 'path/to/ca.crt'
        }
        success, message = self.mqtt_client.connect(config)
        
        # 验证结果
        assert success is True
        mock_client.tls_set.assert_called_once_with('path/to/ca.crt')
        mock_client.connect.assert_called_once_with(
            'localhost', 8883, 60
        )
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_connect_with_auth(self, mock_client_class):
        """测试使用用户名密码连接MQTT服务器"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0
        mock_client_class.return_value = mock_client
        
        # 调用connect方法，设置用户名密码
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': 'test_user',
            'password': 'test_pass',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        success, message = self.mqtt_client.connect(config)
        
        # 验证结果
        assert success is True
        mock_client.username_pw_set.assert_called_once_with('test_user', 'test_pass')
        mock_client.connect.assert_called_once()
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_disconnect(self, mock_client_class):
        """测试断开MQTT连接"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0
        mock_client_class.return_value = mock_client
        
        # 先连接，再断开
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        self.mqtt_client.connect(config)
        # 重置状态回调的调用记录
        self.status_callback.reset_mock()
        
        # 调用disconnect方法
        self.mqtt_client.disconnect()
        
        # 验证结果
        mock_client.loop_stop.assert_called_once()
        mock_client.disconnect.assert_called_once()
        # 验证状态回调被调用，且状态为False
        self.status_callback.assert_called_once_with(False)
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_publish_success(self, mock_client_class):
        """测试成功发布消息"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0
        # 模拟publish返回一个成功的结果
        mock_client.publish.return_value = (0, 1)
        mock_client_class.return_value = mock_client
        
        # 先连接
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        self.mqtt_client.connect(config)
        
        # 调用publish方法
        topic = "test/topic"
        payload = "test message"
        qos = 0
        retain = False
        success, message = self.mqtt_client.publish(topic, payload, qos, retain)
        
        # 验证结果
        assert success is True
        assert message == "消息发布成功"
        mock_client.publish.assert_called_once_with(topic, payload, qos, retain)
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_publish_failure(self, mock_client_class):
        """测试发布消息失败"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0
        # 模拟publish返回一个失败的结果
        mock_client.publish.return_value = (1, None)
        mock_client_class.return_value = mock_client
        
        # 先连接
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        self.mqtt_client.connect(config)
        
        # 调用publish方法
        topic = "test/topic"
        payload = "test message"
        success, message = self.mqtt_client.publish(topic, payload)
        
        # 验证结果
        assert success is False
        assert "发布失败" in message
        mock_client.publish.assert_called_once_with(topic, payload, 0, False)
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_publish_not_connected(self, mock_client_class):
        """测试未连接时发布消息"""
        # 不连接直接发布消息
        topic = "test/topic"
        payload = "test message"
        success, message = self.mqtt_client.publish(topic, payload)
        
        # 验证结果
        assert success is False
        assert message == "MQTT未连接"
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_subscribe_success(self, mock_client_class):
        """测试成功订阅主题"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0
        # 模拟subscribe返回一个成功的结果
        mock_client.subscribe.return_value = (0, 1)
        mock_client_class.return_value = mock_client
        
        # 先连接
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        self.mqtt_client.connect(config)
        
        # 调用subscribe方法
        topic = "test/topic"
        qos = 0
        success, message = self.mqtt_client.subscribe(topic, qos)
        
        # 验证结果
        assert success is True
        assert message == "主题订阅成功"
        mock_client.subscribe.assert_called_once_with(topic, qos)
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_subscribe_failure(self, mock_client_class):
        """测试订阅主题失败"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0
        # 模拟subscribe返回一个失败的结果
        mock_client.subscribe.return_value = (1, None)
        mock_client_class.return_value = mock_client
        
        # 先连接
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        self.mqtt_client.connect(config)
        
        # 调用subscribe方法
        topic = "test/topic"
        success, message = self.mqtt_client.subscribe(topic)
        
        # 验证结果
        assert success is False
        assert "订阅失败" in message
        mock_client.subscribe.assert_called_once_with(topic, 0)
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_unsubscribe(self, mock_client_class):
        """测试取消订阅主题"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0
        mock_client_class.return_value = mock_client
        
        # 先连接
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        self.mqtt_client.connect(config)
        
        # 调用unsubscribe方法
        topic = "test/topic"
        self.mqtt_client.unsubscribe(topic)
        
        # 验证结果
        mock_client.unsubscribe.assert_called_once_with(topic)
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_get_status(self, mock_client_class):
        """测试获取MQTT客户端状态"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0
        mock_client_class.return_value = mock_client
        
        # 初始状态应该是False
        assert self.mqtt_client.get_status() is False
        
        # 连接后状态应该是True
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        self.mqtt_client.connect(config)
        assert self.mqtt_client.get_status() is True
        
        # 断开后状态应该是False
        self.mqtt_client.disconnect()
        assert self.mqtt_client.get_status() is False
    
    @patch('modules.mqtt_client.paho.mqtt.client.Client')
    def test_on_message_callback(self, mock_client_class):
        """测试MQTT消息回调处理"""
        # 模拟paho.mqtt.client.Client返回一个有效的客户端对象
        mock_client = MagicMock()
        mock_client.connect.return_value = 0
        mock_client_class.return_value = mock_client
        
        # 连接MQTT
        config = {
            'broker': 'localhost',
            'port': 1883,
            'username': '',
            'password': '',
            'client_id': 'test_client',
            'keepalive': 60,
            'use_tls': False
        }
        self.mqtt_client.connect(config)
        
        # 获取回调函数
        on_message_callback = mock_client.on_message
        
        # 模拟一个接收到的消息
        mock_message = MagicMock()
        mock_message.topic = "test/topic"
        mock_message.payload = b"test payload"
        
        # 调用回调函数
        on_message_callback(mock_client, None, mock_message)
        
        # 验证数据回调被调用
        self.data_callback.assert_called_once_with({
            'topic': 'test/topic',
            'payload': 'test payload'
        })


if __name__ == "__main__":
    pytest.main(["-v", __file__])