"""
Main HealthChecker client API that orchestrates all components.
"""

import threading
import time
import logging
import schedule
from typing import Dict, Any, Optional, List, Callable
from datetime import datetime

from .core.config import ConfigValidator, ConfigValidationError
from .core.checker import HealthCheckExecutor, HealthCheckResult
from .notifications.email_notifier import EmailNotifier
from .notifications.slack_notifier import SlackNotifier
from .web.dashboard import WebDashboard
from .utils.logger import setup_logging

logger = logging.getLogger(__name__)


class HealthChecker:
    """Main health checker client that orchestrates monitoring of multiple services."""
    
    def __init__(self, config_path: str = "app.json", log_level: str = "INFO", 
                 log_file: Optional[str] = None):
        """
        Initialize HealthChecker client.
        
        Args:
            config_path: Path to configuration file
            log_level: Logging level
            log_file: Optional log file path
        """
        self.config_path = config_path
        self.log_level = log_level
        self.log_file = log_file
        
        # Setup logging
        setup_logging(level=log_level, log_file=log_file)
        
        # Core components
        self.config_validator = ConfigValidator()
        self.executor = None
        self.web_dashboard = None
        
        # Configuration
        self.config = None
        self.services = []
        
        # Notification systems
        self.email_notifiers: Dict[str, EmailNotifier] = {}
        self.slack_notifiers: Dict[str, SlackNotifier] = {}
        
        # Monitoring state
        self.is_running = False
        self.monitoring_thread = None
        self.scheduler = None
        self.dashboard_thread = None
        
        # Status tracking
        self.service_status: Dict[str, HealthCheckResult] = {}
        self.status_history: Dict[str, List[HealthCheckResult]] = {}
        
        # Callbacks
        self.status_callbacks: List[Callable[[HealthCheckResult], None]] = []
        
        self.logger = logging.getLogger(f'{__name__}.{self.__class__.__name__}')
        
        # Load configuration
        self.load_config()
    
    def load_config(self) -> None:
        """
        Load and validate configuration.
        
        Raises:
            ConfigValidationError: If configuration is invalid
        """
        try:
            self.config = self.config_validator.load_config(self.config_path)
            self.services = self.config.get('services', [])
            
            self.logger.info(f"Loaded configuration with {len(self.services)} services")
            
            # Initialize components
            self._initialize_components()
            
        except Exception as e:
            self.logger.error(f"Failed to load configuration: {e}")
            raise
    
    def _initialize_components(self) -> None:
        """Initialize all components based on configuration."""
        # Initialize health check executor
        self.executor = HealthCheckExecutor(callback=self._on_health_check_result)
        
        # Initialize notification systems
        self._initialize_notifications()
        
        # Initialize web dashboard if enabled
        self._initialize_web_dashboard()
    
    def _initialize_notifications(self) -> None:
        """Initialize notification systems for each service."""
        for service in self.services:
            service_name = service['name']
            
            # Email notifier
            email_config = service.get('email_alert', {})
            if email_config.get('enabled', False):
                try:
                    email_notifier = EmailNotifier(email_config)
                    email_notifiers = getattr(self, 'email_notifiers', {})
                    email_notifiers[service_name] = email_notifier
                    self.email_notifiers = email_notifiers
                    
                    # Test email connection
                    if not email_notifier.test_connection():
                        self.logger.warning(f"Email connection test failed for {service_name}")
                    else:
                        self.logger.info(f"Email notifier initialized for {service_name}")
                        
                except Exception as e:
                    self.logger.error(f"Failed to initialize email notifier for {service_name}: {e}")
            
            # Slack notifier
            slack_config = service.get('slack_integration', {})
            if slack_config.get('enabled', False):
                try:
                    slack_notifier = SlackNotifier(slack_config)
                    slack_notifiers = getattr(self, 'slack_notifiers', {})
                    slack_notifiers[service_name] = slack_notifier
                    self.slack_notifiers = slack_notifiers
                    
                    # Test Slack connection
                    if not slack_notifier.test_connection():
                        self.logger.warning(f"Slack connection test failed for {service_name}")
                    else:
                        self.logger.info(f"Slack notifier initialized for {service_name}")
                        
                except Exception as e:
                    self.logger.error(f"Failed to initialize Slack notifier for {service_name}: {e}")
    
    def _initialize_web_dashboard(self) -> None:
        """Initialize web dashboard if any service has it enabled."""
        web_enabled = any(
            service.get('web_interface', {}).get('enabled', False) 
            for service in self.services
        )
        
        if web_enabled:
            try:
                dashboard_config = {
                    'enabled': True,
                    'host': '0.0.0.0',
                    'port': 8080,
                    'debug': False,
                    'max_history_items': 100
                }
                self.web_dashboard = WebDashboard(dashboard_config)
                self.logger.info("Web dashboard initialized on port 8080")
                
            except Exception as e:
                self.logger.error(f"Failed to initialize web dashboard: {e}")
    
    def _on_health_check_result(self, result: HealthCheckResult) -> None:
        """
        Callback function for health check results.
        
        Args:
            result: Health check result
        """
        service_name = result.service_name
        
        # Update status
        previous_status = self.service_status.get(service_name)
        self.service_status[service_name] = result
        
        # Update history
        if service_name not in self.status_history:
            self.status_history[service_name] = []
        self.status_history[service_name].append(result)
        
        # Limit history size
        if len(self.status_history[service_name]) > 100:
            self.status_history[service_name] = self.status_history[service_name][-100:]
        
        # Update web dashboard
        if self.web_dashboard:
            self.web_dashboard.update_service_status(result)
        
        # Send notifications based on status changes
        self._handle_notifications(result, previous_status)
        
        # Execute user callbacks
        for callback in self.status_callbacks:
            try:
                callback(result)
            except Exception as e:
                self.logger.error(f"Error in user callback: {e}")
    
    def _handle_notifications(self, result: HealthCheckResult, 
                             previous_result: Optional[HealthCheckResult]) -> None:
        """
        Handle notifications based on status changes.
        
        Args:
            result: Current health check result
            previous_result: Previous health check result
        """
        service_name = result.service_name
        
        # Check if service status changed to unhealthy/error
        if result.status in ['unhealthy', 'error']:
            if (previous_result is None or 
                previous_result.status == 'healthy' or
                previous_result.status != result.status):
                
                # Service went down - send alert
                self._send_alert(service_name, result)
        
        # Check if service recovered
        elif result.is_healthy():
            if previous_result and not previous_result.is_healthy():
                # Service recovered - send recovery notification
                self._send_recovery_notification(service_name, result)
    
    def _send_alert(self, service_name: str, result: HealthCheckResult) -> None:
        """
        Send alert notifications for a service.
        
        Args:
            service_name: Name of the service
            result: Health check result
        """
        # Send email alert
        email_notifier = self.email_notifiers.get(service_name)
        if email_notifier:
            try:
                email_notifier.on_service_down(result)
            except Exception as e:
                self.logger.error(f"Failed to send email alert for {service_name}: {e}")
        
        # Send Slack alert
        slack_notifier = self.slack_notifiers.get(service_name)
        if slack_notifier:
            try:
                slack_notifier.on_service_down(result)
            except Exception as e:
                self.logger.error(f"Failed to send Slack alert for {service_name}: {e}")
    
    def _send_recovery_notification(self, service_name: str, result: HealthCheckResult) -> None:
        """
        Send recovery notifications for a service.
        
        Args:
            service_name: Name of the service
            result: Health check result
        """
        # Send email recovery notification
        email_notifier = self.email_notifiers.get(service_name)
        if email_notifier:
            try:
                email_notifier.on_service_recovery(result)
            except Exception as e:
                self.logger.error(f"Failed to send email recovery for {service_name}: {e}")
        
        # Send Slack recovery notification
        slack_notifier = self.slack_notifiers.get(service_name)
        if slack_notifier:
            try:
                slack_notifier.on_service_recovery(result)
            except Exception as e:
                self.logger.error(f"Failed to send Slack recovery for {service_name}: {e}")
    
    def _schedule_health_checks(self) -> None:
        """Schedule recurring health checks for all services."""
        # Clear existing schedule
        schedule.clear()
        
        for service in self.services:
            service_name = service['name']
            interval = service.get('interval', 60)
            
            # Schedule health check for this service
            schedule.every(interval).seconds.do(
                self._check_service, service_name
            ).tag(service_name)
            
            self.logger.info(f"Scheduled health check for {service_name} every {interval}s")
    
    def _check_service(self, service_name: str) -> None:
        """
        Check a specific service.
        
        Args:
            service_name: Name of the service to check
        """
        # Find service configuration
        service_config = next(
            (s for s in self.services if s['name'] == service_name), 
            None
        )
        
        if service_config:
            try:
                self.executor.check_service(service_config)
            except Exception as e:
                self.logger.error(f"Error checking service {service_name}: {e}")
    
    def start(self) -> None:
        """
        Start the health monitoring process.
        
        Raises:
            RuntimeError: If monitoring is already running
        """
        if self.is_running:
            raise RuntimeError("Health monitoring is already running")
        
        self.logger.info("Starting health monitoring...")
        
        # Schedule health checks
        self._schedule_health_checks()
        self.logger.info(f"Scheduled {len(schedule.jobs)} health check jobs")
        
        # Start web dashboard if enabled
        if self.web_dashboard:
            self.dashboard_thread = threading.Thread(
                target=self.web_dashboard.run,
                daemon=False,  # Don't use daemon to ensure it doesn't terminate prematurely
                name="WebDashboard"
            )
            self.dashboard_thread.start()
            # Give the dashboard a moment to start up
            time.sleep(0.5)
        
        # Start monitoring thread
        self.monitoring_thread = threading.Thread(
            target=self._run_scheduler,
            daemon=True
        )
        self.monitoring_thread.start()
        
        self.is_running = True
        self.logger.info("Health monitoring started successfully")
        
        # Perform initial health checks
        self.check_all_services()
    
    def _run_scheduler(self) -> None:
        """Run the schedule loop in a separate thread."""
        self.logger.info("Scheduler thread started, monitoring loop running...")
        while self.is_running:
            try:
                schedule.run_pending()
                # Add debug logging to see if scheduler is running
                if schedule.jobs:
                    self.logger.debug(f"Scheduler has {len(schedule.jobs)} jobs. Next run in {schedule.next_run()}")
                time.sleep(1)
            except Exception as e:
                self.logger.error(f"Error in scheduler loop: {e}")
                time.sleep(5)
        self.logger.info("Scheduler thread stopped")
    
    def stop(self) -> None:
        """Stop the health monitoring process."""
        if not self.is_running:
            self.logger.warning("Health monitoring is not running")
            return
        
        self.logger.info("Stopping health monitoring...")
        
        self.is_running = False
        
        # Stop web dashboard
        if self.web_dashboard:
            self.web_dashboard.stop()
            # Wait for dashboard thread to finish
            if self.dashboard_thread and self.dashboard_thread.is_alive():
                self.dashboard_thread.join(timeout=2)
        
        # Clear schedule
        schedule.clear()
        
        # Close executor
        if self.executor:
            self.executor.close()
        
        self.logger.info("Health monitoring stopped")
    
    def check_all_services(self) -> Dict[str, HealthCheckResult]:
        """
        Check all services immediately.
        
        Returns:
            Dictionary of service names to health check results
        """
        if not self.executor:
            raise RuntimeError("Health checker not initialized")
        
        self.logger.info("Checking all services...")
        return self.executor.check_all_services(self.services)
    
    def check_service(self, service_name: str) -> Optional[HealthCheckResult]:
        """
        Check a specific service immediately.
        
        Args:
            service_name: Name of the service to check
            
        Returns:
            Health check result or None if service not found
        """
        service_config = next(
            (s for s in self.services if s['name'] == service_name), 
            None
        )
        
        if not service_config:
            self.logger.warning(f"Service {service_name} not found")
            return None
        
        return self.executor.check_service(service_config)
    
    def get_status(self) -> Dict[str, Dict[str, Any]]:
        """
        Get current status of all services.
        
        Returns:
            Dictionary of service statuses
        """
        return {
            name: result.to_dict() 
            for name, result in self.service_status.items()
        }
    
    def get_service_status(self, service_name: str) -> Optional[Dict[str, Any]]:
        """
        Get status of a specific service.
        
        Args:
            service_name: Name of the service
            
        Returns:
            Service status dictionary or None
        """
        result = self.service_status.get(service_name)
        return result.to_dict() if result else None
    
    def add_status_callback(self, callback: Callable[[HealthCheckResult], None]) -> None:
        """
        Add a callback for status updates.
        
        Args:
            callback: Callback function that receives HealthCheckResult
        """
        self.status_callbacks.append(callback)
    
    def remove_status_callback(self, callback: Callable[[HealthCheckResult], None]) -> None:
        """
        Remove a status callback.
        
        Args:
            callback: Callback function to remove
        """
        if callback in self.status_callbacks:
            self.status_callbacks.remove(callback)
    
    def __enter__(self):
        """Context manager entry."""
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit."""
        self.stop()
    
    def __del__(self):
        """Cleanup on object deletion."""
        self.stop()