"""
Integration tests for health checker components working together.
"""

import pytest
import json
import tempfile
import os
import time
import threading
from unittest.mock import patch, Mock

from openHealth.client import HealthChecker
from openHealth.core.checker import HealthCheckResult
from openHealth import load_config


class TestIntegration:
    """Integration tests for the complete health checker system."""
    
    def setup_method(self):
        """Setup test fixtures."""
        # Create a temporary config file for testing
        self.config_data = {
            "services": [
                {
                    "name": "Test Service 1",
                    "url": "https://httpbin.org",
                    "health_endpoint": "/status/200",
                    "interval": 1,
                    "timeout": 5,
                    "max_retries": 1,
                    "expected_status_code": 200,
                    "web_interface": {
                        "enabled": False
                    },
                    "slack_integration": {
                        "enabled": False,
                        "channel": "test",
                        "api_key": "test-key",
                        "message_template": "Service {name} is down"
                    },
                    "email_alert": {
                        "enabled": False,
                        "recipients": ["test@example.com"],
                        "smtp_server": "smtp.test.com",
                        "smtp_port": 587,
                        "username": "test@test.com",
                        "app_password": "test-password"
                    }
                },
                {
                    "name": "Test Service 2",
                    "url": "https://httpbin.org",
                    "health_endpoint": "/status/404",
                    "interval": 1,
                    "timeout": 5,
                    "max_retries": 1,
                    "expected_status_code": 200,
                    "web_interface": {
                        "enabled": False
                    },
                    "slack_integration": {
                        "enabled": False,
                        "channel": "test",
                        "api_key": "test-key",
                        "message_template": "Service {name} is down"
                    },
                    "email_alert": {
                        "enabled": False,
                        "recipients": ["test@example.com"],
                        "smtp_server": "smtp.test.com",
                        "smtp_port": 587,
                        "username": "test@test.com",
                        "app_password": "test-password"
                    }
                }
            ]
        }
        
        # Write config to temporary file
        self.temp_config = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
        json.dump(self.config_data, self.temp_config, indent=2)
        self.temp_config.close()
    
    def teardown_method(self):
        """Cleanup after tests."""
        os.unlink(self.temp_config.name)
    
    def test_health_checker_initialization(self):
        """Test HealthChecker initialization with valid config."""
        checker = HealthChecker(config_path=self.temp_config.name)
        
        assert checker.config is not None
        assert len(checker.services) == 2
        assert checker.executor is not None
        assert not checker.is_running
        
        checker.stop()
    
    def test_health_checker_initialization_with_direct_config(self):
        """Test HealthChecker initialization with direct config dict."""
        config = load_config(config_path=self.temp_config.name)
        checker = HealthChecker(config=config)
        
        assert checker.config is not None
        assert len(checker.services) == 2
        assert checker.executor is not None
        assert not checker.is_running
        
        checker.stop()
    
    def test_health_checker_initialization_both_parameters_fails(self):
        """Test that providing both config_path and config raises ValueError."""
        config = load_config(config_path=self.temp_config.name)
        
        with pytest.raises(ValueError, match="Cannot specify both"):
            HealthChecker(config_path=self.temp_config.name, config=config)
    
    def test_health_checker_invalid_config(self):
        """Test HealthChecker initialization with invalid config."""
        # Create invalid config
        invalid_config = {"services": [{"name": "Test"}]}  # Missing required fields
        
        temp_invalid = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
        json.dump(invalid_config, temp_invalid)
        temp_invalid.close()
        
        try:
            with pytest.raises(Exception):  # Should raise ConfigValidationError
                HealthChecker(config_path=temp_invalid.name)
        finally:
            os.unlink(temp_invalid.name)
    
    def test_check_all_services_once(self):
        """Test checking all services once."""
        checker = HealthChecker(config_path=self.temp_config.name)
        
        results = checker.check_all_services()
        
        assert len(results) == 2
        assert "Test Service 1" in results
        assert "Test Service 2" in results
        
        # Service 1 should be healthy (200 status)
        service1_result = results["Test Service 1"]
        assert service1_result.is_healthy()
        assert service1_result.status_code == 200
        
        # Service 2 should be unhealthy (404 status)
        service2_result = results["Test Service 2"]
        assert not service2_result.is_healthy()
        assert service2_result.status == "unhealthy"
        assert service2_result.status_code == 404
        
        checker.stop()
    
    def check_single_service(self):
        """Test checking a single service."""
        checker = HealthChecker(config_path=self.temp_config.name)
        
        result = checker.check_service("Test Service 1")
        
        assert result is not None
        assert result.service_name == "Test Service 1"
        assert result.is_healthy()
        
        checker.stop()
    
    def test_check_single_service_with_direct_config(self):
        """Test checking a single service with direct config initialization."""
        config = load_config(config_path=self.temp_config.name)
        checker = HealthChecker(config=config)
        
        result = checker.check_service("Test Service 1")
        
        assert result is not None
        assert result.service_name == "Test Service 1"
        assert result.is_healthy()
        
        checker.stop()
    
    def test_get_status(self):
        """Test getting current status of all services."""
        checker = HealthChecker(config_path=self.temp_config.name)
        
        # Check services to populate status
        checker.check_all_services()
        
        status = checker.get_status()
        
        assert len(status) == 2
        assert "Test Service 1" in status
        assert "Test Service 2" in status
        
        # Check status structure
        for service_name, service_status in status.items():
            assert "service_name" in service_status
            assert "status" in service_status
            assert "response_time_ms" in service_status
            assert "timestamp" in service_status
        
        checker.stop()
    
    def test_status_callbacks(self):
        """Test status callback functionality."""
        callback_results = []
        
        def test_callback(result):
            callback_results.append(result)
        
        checker = HealthChecker(config_path=self.temp_config.name)
        checker.add_status_callback(test_callback)
        
        # Check services
        checker.check_all_services()
        
        # Should have received callbacks for both services
        assert len(callback_results) == 2
        
        service_names = [r.service_name for r in callback_results]
        assert "Test Service 1" in service_names
        assert "Test Service 2" in service_names
        
        # Test callback removal
        checker.remove_status_callback(test_callback)
        callback_results.clear()
        
        checker.check_all_services()
        assert len(callback_results) == 0  # No callbacks should be received
        
        checker.stop()
    
    @patch('openHealth.notifications.email_notifier.smtplib.SMTP')
    def test_email_notification_integration(self, mock_smtp):
        """Test email notification integration."""
        # Mock SMTP server
        mock_server = Mock()
        mock_smtp.return_value.__enter__.return_value = mock_server
        
        # Enable email notifications
        config_with_email = self.config_data.copy()
        config_with_email["services"][0]["email_alert"]["enabled"] = True
        
        temp_config = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
        json.dump(config_with_email, temp_config, indent=2)
        temp_config.close()
        
        try:
            checker = HealthChecker(config_path=temp_config.name)
            
            # Check the service (should succeed, no notification)
            result = checker.check_service("Test Service 1")
            assert result.is_healthy()
            
            # Simulate a failure
            with patch('openHealth.core.checker.requests.Session.get') as mock_get:
                mock_response = Mock()
                mock_response.status_code = 500
                mock_response.raise_for_status.side_effect = Exception("Server Error")
                mock_get.return_value = mock_response
                
                failure_result = checker.check_service("Test Service 1")
                assert not failure_result.is_healthy()
            
            # Check that email was sent for failure
            assert mock_server.send_message.called
            
            checker.stop()
        finally:
            os.unlink(temp_config.name)
    
    @patch('openHealth.notifications.slack_notifier.requests.post')
    def test_slack_notification_integration(self, mock_post):
        """Test Slack notification integration."""
        # Mock Slack API response
        mock_post.return_value.status_code = 200
        mock_post.return_value.json.return_value = {"ok": True}
        
        # Enable Slack notifications
        config_with_slack = self.config_data.copy()
        config_with_slack["services"][0]["slack_integration"]["enabled"] = True
        
        temp_config = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
        json.dump(config_with_slack, temp_config, indent=2)
        temp_config.close()
        
        try:
            checker = HealthChecker(config_path=temp_config.name)
            
            # Simulate a failure
            with patch('openHealth.core.checker.requests.Session.get') as mock_get:
                mock_response = Mock()
                mock_response.status_code = 500
                mock_response.raise_for_status.side_effect = Exception("Server Error")
                mock_get.return_value = mock_response
                
                failure_result = checker.check_service("Test Service 1")
                assert not failure_result.is_healthy()
            
            # Check that Slack was notified
            assert mock_post.called
            
            checker.stop()
        finally:
            os.unlink(temp_config.name)
    
    def test_context_manager(self):
        """Test using HealthChecker as context manager."""
        with HealthChecker(config_path=self.temp_config.name) as checker:
            assert not checker.is_running
            results = checker.check_all_services()
            assert len(results) == 2
        
        # Should be stopped after context exits
        assert not checker.is_running
    
    def test_context_manager_with_direct_config(self):
        """Test using HealthChecker as context manager with direct config."""
        config = load_config(config_path=self.temp_config.name)
        with HealthChecker(config=config) as checker:
            assert not checker.is_running
            results = checker.check_all_services()
            assert len(results) == 2
        
        # Should be stopped after context exits
        assert not checker.is_running
    
    def test_monitoring_start_stop(self):
        """Test starting and stopping monitoring."""
        checker = HealthChecker(config_path=self.temp_config.name)
        
        # Start monitoring
        checker.start()
        assert checker.is_running
        
        # Let it run for a short time
        time.sleep(1.5)
        
        # Stop monitoring
        checker.stop()
        assert not checker.is_running
    
    def test_concurrent_health_checks(self):
        """Test that multiple services can be checked concurrently."""
        # Create config with more services
        config_many = {
            "services": [
                {
                    "name": f"Service {i}",
                    "url": "https://httpbin.org",
                    "health_endpoint": "/status/200",
                    "interval": 1,
                    "timeout": 5,
                    "max_retries": 1,
                    "expected_status_code": 200,
                    "web_interface": {"enabled": False},
                    "slack_integration": {
                        "enabled": False,
                        "channel": "test",
                        "api_key": "test-key",
                        "message_template": "Service {name} is down"
                    },
                    "email_alert": {
                        "enabled": False,
                        "recipients": ["test@example.com"],
                        "smtp_server": "smtp.test.com",
                        "smtp_port": 587,
                        "username": "test@test.com",
                        "app_password": "test-password"
                    }
                }
                for i in range(5)
            ]
        }
        
        temp_config = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
        json.dump(config_many, temp_config, indent=2)
        temp_config.close()
        
        try:
            start_time = time.time()
            checker = HealthChecker(config_path=temp_config.name)
            results = checker.check_all_services()
            end_time = time.time()
            
            # Should complete all checks reasonably quickly (concurrently)
            assert len(results) == 5
            assert end_time - start_time < 10  # Should be faster than sequential
            
            # All should be healthy
            for result in results.values():
                assert result.is_healthy()
            
            checker.stop()
        finally:
            os.unlink(temp_config.name)
    
    def test_state_tracking_across_checks(self):
        """Test that service state is tracked across multiple checks."""
        checker = HealthChecker(config_path=self.temp_config.name)
        
        # First check
        checker.check_all_services()
        status1 = checker.get_status()
        
        # Second check
        checker.check_all_services()
        status2 = checker.get_status()
        
        # Status should be updated
        assert len(status1) == len(status2) == 2
        
        # Timestamps should be different (recent checks)
        for service_name in status1:
            assert status1[service_name]["timestamp"] != status2[service_name]["timestamp"]
            
        # History should be tracked
        assert len(checker.status_history) == 2
        for service_name in checker.status_history:
            assert len(checker.status_history[service_name]) >= 2
        
        checker.stop()
    
    def test_monitoring_with_direct_config(self):
        """Test start/stop monitoring with direct config initialization."""
        config = load_config(config_path=self.temp_config.name)
        checker = HealthChecker(config=config)
        
        # Start monitoring
        checker.start()
        assert checker.is_running
        
        # Let it run for a short time
        time.sleep(1.5)
        
        # Stop monitoring
        checker.stop()
        assert not checker.is_running