"""
端口管理器測試模組

測試 PortManager 類的各項功能，包括：
- 端口可用性檢測
- 進程查找和清理
- 增強端口查找
"""

import pytest
import socket
import time
import threading
import subprocess
import sys
from unittest.mock import patch, MagicMock

# 添加 src 目錄到 Python 路徑
sys.path.insert(0, 'src')

from mcp_feedback_enhanced.web.utils.port_manager import PortManager


class TestPortManager:
    """端口管理器測試類"""
    
    def test_is_port_available_free_port(self):
        """測試檢測空閒端口"""
        # 找一個肯定空閒的端口
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.bind(('127.0.0.1', 0))
            free_port = s.getsockname()[1]
        
        # 測試該端口是否被檢測為可用
        assert PortManager.is_port_available('127.0.0.1', free_port) is True
    
    def test_is_port_available_occupied_port(self):
        """測試檢測被占用的端口"""
        # 創建一個占用端口的 socket
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.bind(('127.0.0.1', 0))
        occupied_port = server_socket.getsockname()[1]
        server_socket.listen(1)
        
        try:
            # 測試該端口是否被檢測為不可用
            assert PortManager.is_port_available('127.0.0.1', occupied_port) is False
        finally:
            server_socket.close()
    
    def test_find_free_port_enhanced_preferred_available(self):
        """測試當偏好端口可用時的行為"""
        # 找一個空閒端口作為偏好端口
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.bind(('127.0.0.1', 0))
            preferred_port = s.getsockname()[1]
        
        # 測試是否返回偏好端口
        result_port = PortManager.find_free_port_enhanced(
            preferred_port=preferred_port,
            auto_cleanup=False
        )
        assert result_port == preferred_port
    
    def test_find_free_port_enhanced_preferred_occupied(self):
        """測試當偏好端口被占用時的行為"""
        # 創建一個占用端口的 socket
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.bind(('127.0.0.1', 0))
        occupied_port = server_socket.getsockname()[1]
        server_socket.listen(1)
        
        try:
            # 測試是否返回其他可用端口
            result_port = PortManager.find_free_port_enhanced(
                preferred_port=occupied_port,
                auto_cleanup=False
            )
            assert result_port != occupied_port
            assert result_port > occupied_port  # 應該向上查找
            
            # 驗證返回的端口確實可用
            assert PortManager.is_port_available('127.0.0.1', result_port) is True
        finally:
            server_socket.close()
    
    def test_find_process_using_port_no_process(self):
        """測試查找沒有進程占用的端口"""
        # 找一個空閒端口
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.bind(('127.0.0.1', 0))
            free_port = s.getsockname()[1]
        
        # 測試是否正確返回 None
        result = PortManager.find_process_using_port(free_port)
        assert result is None
    
    def test_find_process_using_port_with_process(self):
        """測試查找有進程占用的端口"""
        # 創建一個簡單的測試服務器
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.bind(('127.0.0.1', 0))
        test_port = server_socket.getsockname()[1]
        server_socket.listen(1)
        
        try:
            # 測試是否能找到進程信息
            result = PortManager.find_process_using_port(test_port)
            
            if result:  # 如果找到了進程（在某些環境下可能找不到）
                assert isinstance(result, dict)
                assert 'pid' in result
                assert 'name' in result
                assert 'cmdline' in result
                assert isinstance(result['pid'], int)
                assert result['pid'] > 0
        finally:
            server_socket.close()
    
    def test_get_port_status_available(self):
        """測試獲取可用端口的狀態"""
        # 找一個空閒端口
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.bind(('127.0.0.1', 0))
            free_port = s.getsockname()[1]
        
        status = PortManager.get_port_status(free_port)
        
        assert status['port'] == free_port
        assert status['host'] == '127.0.0.1'
        assert status['available'] is True
        assert status['process'] is None
        assert status['error'] is None
    
    def test_get_port_status_occupied(self):
        """測試獲取被占用端口的狀態"""
        # 創建一個占用端口的 socket
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.bind(('127.0.0.1', 0))
        occupied_port = server_socket.getsockname()[1]
        server_socket.listen(1)
        
        try:
            status = PortManager.get_port_status(occupied_port)
            
            assert status['port'] == occupied_port
            assert status['host'] == '127.0.0.1'
            assert status['available'] is False
            # process 可能為 None（取決於系統權限）
            assert status['error'] is None
        finally:
            server_socket.close()
    
    def test_list_listening_ports(self):
        """測試列出監聽端口"""
        # 創建幾個測試服務器
        servers = []
        test_ports = []
        
        try:
            for i in range(2):
                server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                server_socket.bind(('127.0.0.1', 0))
                port = server_socket.getsockname()[1]
                server_socket.listen(1)
                
                servers.append(server_socket)
                test_ports.append(port)
            
            # 測試列出監聽端口
            min_port = min(test_ports) - 10
            max_port = max(test_ports) + 10
            
            listening_ports = PortManager.list_listening_ports(min_port, max_port)
            
            # 驗證結果
            assert isinstance(listening_ports, list)
            
            # 檢查我們的測試端口是否在列表中
            found_ports = [p['port'] for p in listening_ports]
            for test_port in test_ports:
                if test_port in found_ports:
                    # 找到了我們的端口，驗證信息完整性
                    port_info = next(p for p in listening_ports if p['port'] == test_port)
                    assert 'host' in port_info
                    assert 'pid' in port_info
                    assert 'process_name' in port_info
                    assert 'cmdline' in port_info
        
        finally:
            # 清理測試服務器
            for server in servers:
                server.close()
    
    @patch('mcp_feedback_enhanced.web.utils.port_manager.psutil.Process')
    def test_should_cleanup_process_mcp_process(self, mock_process):
        """測試是否應該清理 MCP 相關進程"""
        # 模擬 MCP 相關進程
        process_info = {
            'pid': 1234,
            'name': 'python.exe',
            'cmdline': 'python -m mcp-feedback-enhanced test --web',
            'create_time': time.time(),
            'status': 'running'
        }
        
        result = PortManager._should_cleanup_process(process_info)
        assert result is True
    
    @patch('mcp_feedback_enhanced.web.utils.port_manager.psutil.Process')
    def test_should_cleanup_process_other_process(self, mock_process):
        """測試是否應該清理其他進程"""
        # 模擬其他進程
        process_info = {
            'pid': 5678,
            'name': 'chrome.exe',
            'cmdline': 'chrome --new-window',
            'create_time': time.time(),
            'status': 'running'
        }
        
        result = PortManager._should_cleanup_process(process_info)
        assert result is False
    
    def test_find_free_port_enhanced_max_attempts(self):
        """測試最大嘗試次數限制"""
        # 這個測試比較難實現，因為需要占用大量連續端口
        # 我們只測試參數是否正確傳遞
        try:
            result = PortManager.find_free_port_enhanced(
                preferred_port=65000,  # 使用高端口減少衝突
                auto_cleanup=False,
                max_attempts=10
            )
            assert isinstance(result, int)
            assert 65000 <= result <= 65535
        except RuntimeError:
            # 如果真的找不到端口，這也是正常的
            pass


if __name__ == '__main__':
    # 運行測試
    pytest.main([__file__, '-v'])
