"""
Script principal de monitoring des sauvegardes
Orchestre l'analyse des emails et l'envoi des alertes
"""
import logging
import time
from datetime import datetime, timedelta
import sys
import signal
from logging.handlers import RotatingFileHandler

from config import MONITORING_CONFIG, LOG_CONFIG
from auth import graph_auth
from mail_reader import MailReader
from mail_analyzer import BackupAnalyzer
from mail_sender import AlertSender
from database import BackupDatabase

# Configuration du logging
def setup_logging():
    """Configure le système de logging"""
    logger = logging.getLogger()
    logger.setLevel(getattr(logging, LOG_CONFIG['log_level']))
    
    # Handler pour fichier avec rotation
    file_handler = RotatingFileHandler(
        LOG_CONFIG['log_file'],
        maxBytes=LOG_CONFIG['max_bytes'],
        backupCount=LOG_CONFIG['backup_count']
    )
    file_handler.setFormatter(
        logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    )
    logger.addHandler(file_handler)
    
    # Handler pour console
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(
        logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    )
    logger.addHandler(console_handler)
    
    return logger

logger = setup_logging()

class BackupMonitor:
    """Classe principale pour le monitoring des sauvegardes"""
    
    def __init__(self):
        """Initialise le moniteur"""
        self.running = False
        self.mail_reader = MailReader()
        self.analyzer = BackupAnalyzer()
        self.alert_sender = AlertSender()
        self.database = BackupDatabase()
        
        # Statistiques de session
        self.stats = {
            'cycles': 0,
            'emails_processed': 0,
            'alerts_sent': 0,
            'errors': 0
        }
        
    def start(self):
        """Démarre le monitoring"""
        logger.info("=== Démarrage du monitoring des sauvegardes ===")
        
        # Tester les connexions
        if not self.test_connections():
            logger.error("Échec des tests de connexion. Arrêt.")
            return False
        
        # Configurer l'arrêt propre
        signal.signal(signal.SIGINT, self.stop)
        signal.signal(signal.SIGTERM, self.stop)
        
        self.running = True
        logger.info("Monitoring démarré avec succès")
        
        # Boucle principale
        self.run_monitoring_loop()
        
        return True
    
    def stop(self, signum=None, frame=None):
        """Arrête le monitoring proprement"""
        logger.info("Arrêt du monitoring demandé...")
        self.running = False
        
        # Afficher les statistiques finales
        self.print_statistics()
        
        logger.info("Monitoring arrêté")
        sys.exit(0)
    
    def test_connections(self):
        """Teste toutes les connexions nécessaires"""
        logger.info("Test des connexions...")
        
        # Test Microsoft Graph
        if not graph_auth.test_connection():
            logger.error("Échec de connexion à Microsoft Graph")
            return False
        
        # Test accès boîte mail
        if not self.mail_reader.test_mailbox_access():
            logger.error("Échec d'accès à la boîte mail")
            return False
        
        logger.info("Tous les tests de connexion réussis")
        return True
    
    def run_monitoring_loop(self):
        """Boucle principale de monitoring"""
        while self.running:
            try:
                cycle_start = time.time()
                self.stats['cycles'] += 1
                
                logger.info(f"=== Cycle {self.stats['cycles']} démarré ===")
                
                # Exécuter le cycle de monitoring
                self.run_monitoring_cycle()
                
                # Nettoyage périodique (tous les 10 cycles)
                if self.stats['cycles'] % 10 == 0:
                    self.perform_maintenance()
                
                # Rapport quotidien (à 9h du matin)
                if datetime.now().hour == 9 and datetime.now().minute < 30:
                    self.send_daily_report()
                
                cycle_duration = time.time() - cycle_start
                logger.info(f"Cycle {self.stats['cycles']} terminé en {cycle_duration:.2f}s")
                
                # Attendre avant le prochain cycle
                sleep_time = MONITORING_CONFIG['check_interval_minutes'] * 60
                logger.info(f"Attente de {MONITORING_CONFIG['check_interval_minutes']} minutes...")
                time.sleep(sleep_time)
                
            except KeyboardInterrupt:
                self.stop()
            except Exception as e:
                logger.error(f"Erreur dans la boucle principale: {str(e)}")
                self.stats['errors'] += 1
                time.sleep(60)  # Attendre 1 minute en cas d'erreur
    
    def run_monitoring_cycle(self):
        """Exécute un cycle complet de monitoring"""
        try:
            # 1. Récupérer les emails récents
            logger.info("Récupération des emails récents...")
            recent_emails = self.mail_reader.get_recent_emails(hours=48)
            
            if not recent_emails:
                logger.warning("Aucun email récent trouvé")
                return
            
            logger.info(f"Trouvé {len(recent_emails)} emails à analyser")
            
            # 2. Analyser les emails
            analyzed_backups = []
            for email in recent_emails:
                analyzed = self.analyzer.extract_backup_info(email)
                if analyzed:
                    analyzed_backups.append(analyzed)
                    # Sauvegarder dans la base de données
                    self.database.insert_backup_record(analyzed)
                    self.stats['emails_processed'] += 1
            
            logger.info(f"Analysé {len(analyzed_backups)} sauvegardes")
            
            # 3. Vérifier les échecs récents
            failed_backups = self.analyzer.get_failed_backups(analyzed_backups)
            if failed_backups:
                logger.warning(f"Détecté {len(failed_backups)} sauvegardes en échec")
                for backup in failed_backups:
                    # Envoyer une alerte pour chaque échec récent
                    if self.is_recent_failure(backup):
                        if self.alert_sender.send_failure_alert(backup):
                            self.stats['alerts_sent'] += 1
                            self.database.log_alert('failure', backup['backup_name'])
            
            # 4. Vérifier les sauvegardes manquantes
            missing_backups = self.database.check_missing_backups()
            if missing_backups:
                logger.warning(f"Détecté {len(missing_backups)} sauvegardes manquantes")
                if self.alert_sender.send_missing_alert(missing_backups):
                    self.stats['alerts_sent'] += 1
                    self.database.log_alert('missing', details=missing_backups)
            
            # 5. Mettre à jour les statuts
            self.update_backup_status(analyzed_backups)
            
        except Exception as e:
            logger.error(f"Erreur pendant le cycle de monitoring: {str(e)}")
            self.stats['errors'] += 1
    
    def is_recent_failure(self, backup):
        """
        Vérifie si un échec est récent (moins de 2 heures)
        
        Args:
            backup: Infos de la sauvegarde
            
        Returns:
            bool: True si échec récent
        """
        try:
            received_date = datetime.fromisoformat(
                backup['received_date'].replace('Z', '+00:00')
            )
            age = datetime.now(received_date.tzinfo) - received_date
            return age.total_seconds() < 7200  # 2 heures
        except:
            return False
    
    def update_backup_status(self, analyzed_backups):
        """Met à jour le statut global des sauvegardes"""
        status_summary = {
            'total': len(analyzed_backups),
            'success': sum(1 for b in analyzed_backups if b['status'] == 'success'),
            'failure': sum(1 for b in analyzed_backups if b['status'] == 'failure'),
            'unknown': sum(1 for b in analyzed_backups if b['status'] == 'unknown'),
        }
        
        logger.info(f"Résumé: {status_summary}")
        
        # Alerter si trop d'échecs
        if status_summary['failure'] > 3:
            logger.critical(f"ATTENTION: {status_summary['failure']} sauvegardes en échec!")
    
    def send_daily_report(self):
        """Envoie le rapport quotidien"""
        try:
            # Vérifier si déjà envoyé aujourd'hui
            today = datetime.now().date()
            if hasattr(self, 'last_daily_report') and self.last_daily_report == today:
                return
            
            logger.info("Préparation du rapport quotidien...")
            
            # Collecter les statistiques
            stats = self.database.get_statistics(days=1)
            failed = self.database.get_failed_backups(hours=24)
            missing = self.database.check_missing_backups()
            
            summary_data = {
                'total': stats.get('total', 0),
                'success': stats.get('by_status', {}).get('success', 0),
                'failed': stats.get('by_status', {}).get('failure', 0),
                'missing': missing,
                'failed_list': failed
            }
            
            # Envoyer le rapport
            if self.alert_sender.send_summary_report(summary_data):
                self.database.log_alert('summary', details=summary_data)
                self.last_daily_report = today
                logger.info("Rapport quotidien envoyé avec succès")
            
        except Exception as e:
            logger.error(f"Erreur lors de l'envoi du rapport quotidien: {str(e)}")
    
    def perform_maintenance(self):
        """Effectue la maintenance périodique"""
        try:
            logger.info("Maintenance périodique...")
            
            # Nettoyer les vieux enregistrements
            deleted = self.database.cleanup_old_records(days=90)
            logger.info(f"Nettoyage: {deleted} enregistrements supprimés")
            
            # Récupérer tous les emails pour mise à jour complète
            all_emails = self.mail_reader.get_all_backup_emails(days=7)
            if all_emails:
                analyzed = []
                for email in all_emails:
                    info = self.analyzer.extract_backup_info(email)
                    if info:
                        analyzed.append(info)
                
                # Mise à jour en masse
                self.database.bulk_insert_records(analyzed)
                logger.info(f"Base de données mise à jour avec {len(analyzed)} enregistrements")
            
        except Exception as e:
            logger.error(f"Erreur pendant la maintenance: {str(e)}")
    
    def print_statistics(self):
        """Affiche les statistiques de la session"""
        logger.info("=== Statistiques de session ===")
        logger.info(f"Cycles effectués: {self.stats['cycles']}")
        logger.info(f"Emails traités: {self.stats['emails_processed']}")
        logger.info(f"Alertes envoyées: {self.stats['alerts_sent']}")
        logger.info(f"Erreurs rencontrées: {self.stats['errors']}")
        
        # Statistiques de la base de données
        db_stats = self.database.get_statistics(days=30)
        if db_stats:
            logger.info(f"Taux de succès (30j): {db_stats.get('success_rate', 0):.1f}%")
            logger.info(f"Sauvegardes uniques: {db_stats.get('unique_backups', 0)}")

def main():
    """Point d'entrée principal"""
    monitor = BackupMonitor()
    
    try:
        monitor.start()
    except Exception as e:
        logger.critical(f"Erreur fatale: {str(e)}")
        sys.exit(1)

if __name__ == "__main__":
    main()