"""
Tests for health check execution module.
"""

import pytest
import time
from unittest.mock import Mock, patch, MagicMock
from requests import Response

from openHealth.core.checker import HealthCheckExecutor, HealthCheckResult


class TestHealthCheckResult:
    """Test cases for HealthCheckResult class."""
    
    def test_health_check_result_creation(self):
        """Test creating HealthCheckResult with various parameters."""
        result = HealthCheckResult(
            service_name="Test Service",
            url="https://example.com/health",
            status="healthy",
            response_time=0.123,
            error_message=None,
            status_code=200
        )
        
        assert result.service_name == "Test Service"
        assert result.url == "https://example.com/health"
        assert result.status == "healthy"
        assert result.response_time == 0.123
        assert result.error_message is None
        assert result.status_code == 200
        assert result.timestamp is not None
    
    def test_health_check_result_to_dict(self):
        """Test converting HealthCheckResult to dictionary."""
        result = HealthCheckResult(
            service_name="Test Service",
            url="https://example.com/health",
            status="unhealthy",
            response_time=0.456,
            error_message="Service unavailable",
            status_code=503
        )
        
        result_dict = result.to_dict()
        
        assert result_dict['service_name'] == "Test Service"
        assert result_dict['url'] == "https://example.com/health"
        assert result_dict['status'] == "unhealthy"
        assert result_dict['response_time_ms'] == 456.0
        assert result_dict['error_message'] == "Service unavailable"
        assert result_dict['status_code'] == 503
        assert 'timestamp' in result_dict
    
    def test_health_check_result_is_healthy(self):
        """Test is_healthy method."""
        healthy_result = HealthCheckResult("Service", "URL", "healthy", 0.1)
        assert healthy_result.is_healthy()
        
        unhealthy_result = HealthCheckResult("Service", "URL", "unhealthy", 0.1)
        assert not unhealthy_result.is_healthy()
        
        error_result = HealthCheckResult("Service", "URL", "error", 0.1)
        assert not error_result.is_healthy()


class TestHealthCheckExecutor:
    """Test cases for HealthCheckExecutor class."""
    
    def setup_method(self):
        """Setup test fixtures."""
        self.executor = HealthCheckExecutor()
    
    def teardown_method(self):
        """Cleanup after tests."""
        if self.executor:
            self.executor.close()
    
    def test_executor_initialization(self):
        """Test HealthCheckExecutor initialization."""
        assert self.executor.session is not None
        assert self.executor.callback is None
    
    def test_executor_initialization_with_callback(self):
        """Test HealthCheckExecutor initialization with callback."""
        mock_callback = Mock()
        executor = HealthCheckExecutor(callback=mock_callback)
        assert executor.callback == mock_callback
        executor.close()
    
    @patch('openHealth.core.checker.requests.Session.get')
    def test_check_service_success(self, mock_get):
        """Test successful health check."""
        # Mock successful response
        mock_response = Mock(spec=Response)
        mock_response.status_code = 200
        mock_response.raise_for_status.return_value = None
        mock_get.return_value = mock_response
        
        service_config = {
            "name": "Test Service",
            "url": "https://example.com",
            "health_endpoint": "/health",
            "timeout": 10,
            "expected_status_code": 200,
            "max_retries": 3
        }
        
        result = self.executor.check_service(service_config)
        
        assert result.service_name == "Test Service"
        assert result.status == "healthy"
        assert result.is_healthy()
        assert result.status_code == 200
        assert result.error_message is None
        assert result.url == "https://example.com/health"
        assert result.response_time >= 0
    
    @patch('openHealth.core.checker.requests.Session.get')
    def test_check_service_wrong_status_code(self, mock_get):
        """Test health check with unexpected status code."""
        # Mock response with wrong status code
        mock_response = Mock(spec=Response)
        mock_response.status_code = 404
        mock_response.raise_for_status.return_value = None
        mock_get.return_value = mock_response
        
        service_config = {
            "name": "Test Service",
            "url": "https://example.com",
            "health_endpoint": "/health",
            "timeout": 10,
            "expected_status_code": 200,
            "max_retries": 3
        }
        
        result = self.executor.check_service(service_config)
        
        assert result.service_name == "Test Service"
        assert result.status == "unhealthy"
        assert not result.is_healthy()
        assert result.status_code == 404
        assert "Unexpected status code" in result.error_message
    
    @patch('openHealth.core.checker.requests.Session.get')
    def test_check_service_timeout(self, mock_get):
        """Test health check with timeout."""
        import requests
        mock_get.side_effect = requests.exceptions.Timeout("Request timed out")
        
        service_config = {
            "name": "Test Service",
            "url": "https://example.com",
            "health_endpoint": "/health",
            "timeout": 10,
            "expected_status_code": 200,
            "max_retries": 3
        }
        
        result = self.executor.check_service(service_config)
        
        assert result.service_name == "Test Service"
        assert result.status == "error"
        assert not result.is_healthy()
        assert "timeout" in result.error_message.lower()
        assert result.response_time == 10  # Should equal timeout
    
    @patch('openHealth.core.checker.requests.Session.get')
    def test_check_service_connection_error(self, mock_get):
        """Test health check with connection error."""
        import requests
        mock_get.side_effect = requests.exceptions.ConnectionError("Connection failed")
        
        service_config = {
            "name": "Test Service",
            "url": "https://example.com",
            "health_endpoint": "/health",
            "timeout": 10,
            "expected_status_code": 200,
            "max_retries": 3
        }
        
        result = self.executor.check_service(service_config)
        
        assert result.service_name == "Test Service"
        assert result.status == "error"
        assert not result.is_healthy()
        assert "Connection error" in result.error_message
    
    @patch('openHealth.core.checker.requests.Session.get')
    def test_check_service_http_error(self, mock_get):
        """Test health check with HTTP error."""
        import requests
        mock_get.side_effect = requests.exceptions.HTTPError("500 Server Error")
        
        service_config = {
            "name": "Test Service",
            "url": "https://example.com",
            "health_endpoint": "/health",
            "timeout": 10,
            "expected_status_code": 200,
            "max_retries": 3
        }
        
        result = self.executor.check_service(service_config)
        
        assert result.service_name == "Test Service"
        assert result.status == "error"
        assert not result.is_healthy()
        assert "Request error" in result.error_message
    
    @patch('openHealth.core.checker.requests.Session.get')
    def test_check_service_general_exception(self, mock_get):
        """Test health check with general exception."""
        mock_get.side_effect = Exception("Unexpected error")
        
        service_config = {
            "name": "Test Service",
            "url": "https://example.com",
            "health_endpoint": "/health",
            "timeout": 10,
            "expected_status_code": 200,
            "max_retries": 3
        }
        
        result = self.executor.check_service(service_config)
        
        assert result.service_name == "Test Service"
        assert result.status == "error"
        assert not result.is_healthy()
        assert "Unexpected error" in result.error_message
    
    def test_check_service_url_construction(self):
        """Test URL construction for health checks."""
        with patch('openHealth.core.checker.requests.Session.get') as mock_get:
            mock_response = Mock(spec=Response)
            mock_response.status_code = 200
            mock_response.raise_for_status.return_value = None
            mock_get.return_value = mock_response
            
            # Test various URL combinations
            test_cases = [
                ("https://example.com", "/health", "https://example.com/health"),
                ("https://example.com/", "/health", "https://example.com/health"),
                ("https://example.com", "health", "https://example.com/health"),
                ("https://example.com/", "health", "https://example.com/health"),
                ("https://example.com/api", "/v1/health", "https://example.com/api/v1/health"),
            ]
            
            for base_url, endpoint, expected_url in test_cases:
                service_config = {
                    "name": "Test Service",
                    "url": base_url,
                    "health_endpoint": endpoint,
                    "timeout": 10,
                    "expected_status_code": 200,
                    "max_retries": 0  # No retries for faster test
                }
                
                result = self.executor.check_service(service_config)
                
                # Check that the correct URL was called
                mock_get.assert_called_with(expected_url, timeout=10, allow_redirects=True)
                assert result.url == expected_url
    
    @patch('openHealth.core.checker.requests.Session.get')
    def test_check_all_services(self, mock_get):
        """Test checking multiple services."""
        # Mock successful response for all services
        mock_response = Mock(spec=Response)
        mock_response.status_code = 200
        mock_response.raise_for_status.return_value = None
        mock_get.return_value = mock_response
        
        services = [
            {
                "name": "Service 1",
                "url": "https://service1.example.com",
                "health_endpoint": "/health",
                "timeout": 5,
                "expected_status_code": 200,
                "max_retries": 1
            },
            {
                "name": "Service 2",
                "url": "https://service2.example.com",
                "health_endpoint": "/status",
                "timeout": 10,
                "expected_status_code": 200,
                "max_retries": 1
            }
        ]
        
        results = self.executor.check_all_services(services)
        
        assert len(results) == 2
        assert "Service 1" in results
        assert "Service 2" in results
        
        # Both should be healthy
        for result in results.values():
            assert result.is_healthy()
            assert result.status_code == 200
    
    def test_callback_execution(self):
        """Test that callback is executed with health check results."""
        mock_callback = Mock()
        executor = HealthCheckExecutor(callback=mock_callback)
        
        with patch('openHealth.core.checker.requests.Session.get') as mock_get:
            mock_response = Mock(spec=Response)
            mock_response.status_code = 200
            mock_response.raise_for_status.return_value = None
            mock_get.return_value = mock_response
            
            service_config = {
                "name": "Test Service",
                "url": "https://example.com",
                "health_endpoint": "/health",
                "timeout": 10,
                "expected_status_code": 200,
                "max_retries": 0
            }
            
            result = executor.check_service(service_config)
            
            # Callback should be called with the result
            mock_callback.assert_called_once_with(result)
        
        executor.close()
    
    def test_callback_exception_handling(self):
        """Test that exceptions in callback don't break health checks."""
        # Callback that raises exception
        def failing_callback(result):
            raise Exception("Callback failed")
        
        executor = HealthCheckExecutor(callback=failing_callback)
        
        with patch('openHealth.core.checker.requests.Session.get') as mock_get:
            mock_response = Mock(spec=Response)
            mock_response.status_code = 200
            mock_response.raise_for_status.return_value = None
            mock_get.return_value = mock_response
            
            service_config = {
                "name": "Test Service",
                "url": "https://example.com",
                "health_endpoint": "/health",
                "timeout": 10,
                "expected_status_code": 200,
                "max_retries": 0
            }
            
            # Should not raise exception despite callback failure
            result = executor.check_service(service_config)
            assert result.is_healthy()
        
        executor.close()
    
    def test_session_headers(self):
        """Test that HTTP session has appropriate headers."""
        session = self.executor.session
        
        expected_headers = [
            'User-Agent',
            'Accept',
            'Cache-Control'
        ]
        
        for header in expected_headers:
            assert header in session.headers
        
        assert 'HealthChecker' in session.headers['User-Agent']
    
    def test_close_session(self):
        """Test closing HTTP session."""
        # Session should exist initially
        assert self.executor.session is not None
        
        # Close session
        self.executor.close()
        
        # Session should still exist but be closed
        assert hasattr(self.executor, 'session')
    
    def test_default_config_values(self):
        """Test that default configuration values are used when not specified."""
        with patch('openHealth.core.checker.requests.Session.get') as mock_get:
            mock_response = Mock(spec=Response)
            mock_response.status_code = 200
            mock_response.raise_for_status.return_value = None
            mock_get.return_value = mock_response
            
            # Minimal config with only required fields
            service_config = {
                "name": "Test Service",
                "url": "https://example.com",
                "health_endpoint": "/health"
                # No timeout, expected_status_code, or max_retries
            }
            
            result = self.executor.check_service(service_config)
            
            # Should use default values
            mock_get.assert_called_once_with(
                "https://example.com/health",
                timeout=10,  # Default timeout
                allow_redirects=True
            )
            assert result.is_healthy()