# Generated by Copilot
# Maintained by Dinesh Kumar C (https://github.com/Dinesh210805/blender-mcp)
# Forked from original work by Siddharth Ahuja (https://github.com/ahujasid/blender-mcp)

"""Tests for connection management."""

import pytest
import socket
import json
from unittest.mock import Mock, patch, MagicMock

from src.blender_mcp.connection import BlenderConnection, ConnectionManager
from src.blender_mcp.exceptions import ConnectionError, TimeoutError, BlenderOperationError


class TestBlenderConnection:
    """Test BlenderConnection class."""
    
    def test_init(self):
        """Test connection initialization."""
        conn = BlenderConnection(host="test", port=1234, timeout=10.0)
        assert conn.host == "test"
        assert conn.port == 1234
        assert conn.timeout == 10.0
        assert conn._sock is None
    
    @patch('socket.socket')
    def test_connect_success(self, mock_socket_class):
        """Test successful connection."""
        mock_socket = Mock()
        mock_socket_class.return_value = mock_socket
        
        conn = BlenderConnection(retry_attempts=1)
        result = conn.connect()
        
        assert result is True
        assert conn._sock == mock_socket
        mock_socket.connect.assert_called_once_with((conn.host, conn.port))
    
    @patch('socket.socket')
    def test_connect_failure(self, mock_socket_class):
        """Test connection failure."""
        mock_socket = Mock()
        mock_socket.connect.side_effect = socket.error("Connection failed")
        mock_socket_class.return_value = mock_socket
        
        conn = BlenderConnection(retry_attempts=1, retry_delay=0.01)
        
        with pytest.raises(ConnectionError):
            conn.connect()
    
    @patch('socket.socket')
    def test_connect_retry_logic(self, mock_socket_class):
        """Test connection retry logic."""
        mock_socket = Mock()
        # First attempt fails, second succeeds
        mock_socket.connect.side_effect = [socket.error("Failed"), None]
        mock_socket_class.return_value = mock_socket
        
        conn = BlenderConnection(retry_attempts=2, retry_delay=0.01)
        result = conn.connect()
        
        assert result is True
        assert mock_socket.connect.call_count == 2
    
    def test_disconnect(self):
        """Test disconnection."""
        conn = BlenderConnection()
        mock_socket = Mock()
        conn._sock = mock_socket
        
        conn.disconnect()
        
        mock_socket.close.assert_called_once()
        assert conn._sock is None
    
    def test_is_socket_alive(self):
        """Test socket alive check."""
        conn = BlenderConnection()
        
        # No socket
        assert conn._is_socket_alive() is False
        
        # Socket with no error
        mock_socket = Mock()
        mock_socket.getsockopt.return_value = 0
        conn._sock = mock_socket
        assert conn._is_socket_alive() is True
        
        # Socket with error
        mock_socket.getsockopt.return_value = 1
        assert conn._is_socket_alive() is False
    
    def test_receive_complete_response(self):
        """Test receiving complete response."""
        conn = BlenderConnection()
        mock_socket = Mock()
        
        # Simulate receiving JSON in chunks
        response_data = {"status": "success", "result": {"test": "data"}}
        response_json = json.dumps(response_data).encode('utf-8')
        
        # Split into chunks
        chunk1 = response_json[:10]
        chunk2 = response_json[10:]
        
        mock_socket.recv.side_effect = [chunk1, chunk2, b'']
        conn._sock = mock_socket
        
        result = conn._receive_complete_response()
        assert result == response_json
    
    def test_receive_timeout(self):
        """Test receive timeout."""
        conn = BlenderConnection(timeout=0.1)
        mock_socket = Mock()
        mock_socket.recv.side_effect = socket.timeout()
        conn._sock = mock_socket
        
        with pytest.raises(BlenderOperationError):
            conn._receive_complete_response()
    
    @patch('src.blender_mcp.connection.execute_blender_command')
    def test_send_command_success(self, mock_execute):
        """Test successful command sending."""
        mock_execute.return_value = {"test": "result"}
        
        conn = BlenderConnection()
        mock_socket = Mock()
        conn._sock = mock_socket
        
        # Mock successful response
        response_data = {"status": "success", "result": {"test": "result"}}
        response_json = json.dumps(response_data).encode('utf-8')
        mock_socket.recv.return_value = response_json
        
        with patch.object(conn, '_receive_complete_response', return_value=response_json):
            result = conn.send_command("test_command", {"param": "value"})
        
        assert result == {"test": "result"}
        mock_socket.sendall.assert_called_once()
    
    def test_send_command_blender_error(self):
        """Test Blender error response."""
        conn = BlenderConnection()
        mock_socket = Mock()
        conn._sock = mock_socket
        
        # Mock error response
        response_data = {"status": "error", "message": "Blender error"}
        response_json = json.dumps(response_data).encode('utf-8')
        
        with patch.object(conn, '_receive_complete_response', return_value=response_json):
            with pytest.raises(BlenderOperationError):
                conn.send_command("test_command")


class TestConnectionManager:
    """Test ConnectionManager class."""
    
    def test_init(self):
        """Test manager initialization."""
        manager = ConnectionManager()
        assert manager._connection is None
    
    @patch('src.blender_mcp.connection.BlenderConnection')
    def test_get_connection_new(self, mock_connection_class):
        """Test getting new connection."""
        mock_connection = Mock()
        mock_connection.connect.return_value = True
        mock_connection_class.return_value = mock_connection
        
        manager = ConnectionManager()
        result = manager.get_connection()
        
        assert result == mock_connection
        mock_connection.connect.assert_called_once()
    
    @patch('src.blender_mcp.connection.BlenderConnection')
    def test_get_connection_existing(self, mock_connection_class):
        """Test getting existing connection."""
        mock_connection = Mock()
        mock_connection.connect.return_value = True
        mock_connection_class.return_value = mock_connection
        
        manager = ConnectionManager()
        manager._connection = mock_connection
        
        result = manager.get_connection()
        
        assert result == mock_connection
        mock_connection.connect.assert_called_once()
    
    def test_close_connection(self):
        """Test closing connection."""
        manager = ConnectionManager()
        mock_connection = Mock()
        manager._connection = mock_connection
        
        manager.close_connection()
        
        mock_connection.disconnect.assert_called_once()
        assert manager._connection is None
    
    @patch('src.blender_mcp.connection.BlenderConnection')
    def test_execute_command(self, mock_connection_class):
        """Test command execution."""
        mock_connection = Mock()
        mock_connection.connect.return_value = True
        mock_connection.send_command.return_value = {"result": "success"}
        mock_connection_class.return_value = mock_connection
        
        manager = ConnectionManager()
        result = manager.execute_command("test_command", {"param": "value"})
        
        assert result == {"result": "success"}
        mock_connection.send_command.assert_called_once_with("test_command", {"param": "value"})
