"""
Client gRPC principal - VERSION REFACTORISÉE SANS REDONDANCES
"""
import os
import time
import threading
import signal
from typing import Optional, Dict, Any

from .connection import GrpcConnection
from .state import ConnectionState
from .retry import RetryManager
from .handlers import ControlChannelHandler, LogHandler
from .handlers.logs import DaemonLogHandler
from .exceptions import GrpcConnectionError
from ...monitoring.logger import logger


class GrpcClient:
    """
    Client gRPC principal avec gestion du shutdown automatique
    
    Responsabilités:
    - Orchestration des connexions via GrpcConnection
    - Gestion des handlers spécialisés (LogHandler/DaemonLogHandler)
    - Shutdown automatique après échecs de connexion
    - Interface simplifiée pour le SDK Lescopr
    
    ARCHITECTURE CLEAN:
    - ✅ PAS de queue logs (déléguée à Lescopr)
    - ✅ send_log() = proxy intelligent vers handlers
    - ✅ Séparation claire mode gRPC vs daemon
    """
    
    #DEFAULT_HOST = '127.0.0.1'
    DEFAULT_HOST = 'api.lescopr.com'
    DEFAULT_PORT = 50051
    
    def __init__(self, lescopr_client, auto_connect=True, daemon_mode=False):
        self.lescopr_client = lescopr_client
        self.daemon_mode = daemon_mode
        
        self.connection: Optional[GrpcConnection] = GrpcConnection(daemon_mode=daemon_mode)
        self.retry_manager = RetryManager(max_attempts=5, base_delay=2.0)
        
        # Handlers spécialisés (UN SEUL selon le mode)
        self.control_handler: Optional[ControlChannelHandler] = None
        self.log_handler: Optional[LogHandler] = None  # OU DaemonLogHandler selon mode
        
        # Gestion des threads et shutdown
        self._threads: Dict[str, threading.Thread] = {}
        self._shutdown_event = threading.Event()
        self._shutdown_requested = False
        self._shutdown_lock = threading.Lock()
    
        # ✅ CONNEXION DIFFÉRÉE SELON LE MODE
        if auto_connect and not daemon_mode:
            pass
        elif daemon_mode:
            logger('info', "[GRPC] Mode daemon - prêt pour attachement")

    def initialize(self, host: str = None, port: int = None) -> bool:
        """
        Initialise le client gRPC avec shutdown automatique
        
        Args:
            host: Adresse du serveur
            port: Port du serveur
            
        Returns:
            bool: True si l'initialisation réussit
        """
        try:
            config = self._load_connection_config(host, port)
            
            # ✅ CRÉER CONNEXION AVEC CALLBACK SHUTDOWN
            self.connection = GrpcConnection()
            
            connect_operation = lambda: self.connection.connect(
                config['host'], 
                config['port'], 
                shutdown_callback=self._trigger_shutdown
            )
            
            if not self.retry_manager.retry_with_backoff(connect_operation, "connexion gRPC"):
                raise GrpcConnectionError("Échec de connexion après retry")
            
            self._initialize_handlers()
            self._start_worker_threads()
            
            logger('info', "[GRPC] Client gRPC initialisé avec auto-shutdown")
            return True
            
        except Exception as e:
            logger('error', f"[GRPC] Erreur initialisation: {e}")
            self._cleanup_on_error()
            return False

    def send_log(self, level: str, message: str, metadata: Dict = None) -> bool:
        """
        ✅ PROXY INTELLIGENT - DÉLÈGUE SELON LE MODE
        
        - Mode daemon: Via DaemonLogHandler (socket Unix/file)
        - Mode gRPC: Via LogHandler (qui utilise queue centrale Lescopr)
        - Fallback: Directement vers queue centrale Lescopr
        """
        try:
            if self.daemon_mode and self.log_handler:
                # ✅ MODE DAEMON : Envoi direct via DaemonLogHandler
                return self.log_handler.add_log(level, message, metadata)
            
            elif not self.daemon_mode and self.log_handler:
                # ✅ MODE GRPC : Via LogHandler qui utilise queue Lescopr
                return self.log_handler.add_log(level, message, metadata)
            
            else:
                # ✅ FALLBACK : Directement vers queue centrale Lescopr
                if hasattr(self, 'lescopr_client') and self.lescopr_client:
                    log_entry = {
                        'level': level,
                        'message': message,
                        'metadata': metadata or {},
                        'timestamp': time.time()
                    }
                    self.lescopr_client.add_pending_log(log_entry)
                    return True
                return False
                
        except Exception as e:
            logger('error', f"[GRPC] Erreur send_log: {e}")
            return False

    def send_metric(self, name: str, value: float, labels: Dict = None) -> bool:
        """Envoie une métrique via le handler approprié"""
        # TODO: Implémenter selon le même pattern que send_log
        return False

    def _trigger_shutdown(self):
        """
        Callback déclenché par GrpcConnection en cas d'échecs répétés
        
        Utilise un mécanisme thread-safe pour éviter les appels multiples
        """
        with self._shutdown_lock:
            if self._shutdown_requested:
                return
            
            self._shutdown_requested = True
            logger('critical', "[GRPC-CLIENT] 🛑 Shutdown automatique initié")
        
        # ✅ EXÉCUTER SHUTDOWN EN BACKGROUND POUR ÉVITER BLOCAGE
        shutdown_thread = threading.Thread(
            target=self._execute_shutdown,
            name="GrpcShutdown",
            daemon=True
        )
        shutdown_thread.start()
    
    def _execute_shutdown(self):
        """
        Exécute le shutdown de manière ordonnée
        
        Séparé de _trigger_shutdown pour éviter les deadlocks
        """
        try:
            logger('info', "[GRPC-CLIENT] Début procédure shutdown...")
            
            # 1. Signaler l'arrêt aux composants
            self._shutdown_event.set()
            
            # 2. Arrêter les handlers proprement
            self._stop_handlers_gracefully()
            
            # 3. Fermer la connexion
            self._close_connection()
            
            # 4. Déclencher shutdown système
            self._trigger_system_shutdown()
            
        except Exception as e:
            logger('error', f"[GRPC-CLIENT] Erreur pendant shutdown: {e}")
            self._force_exit()
    
    def _stop_handlers_gracefully(self):
        """Arrête les handlers avec timeout"""
        logger('debug', "[GRPC-CLIENT] Arrêt des handlers...")
        
        handlers = [
            ('control', self.control_handler),
            ('log', self.log_handler)
        ]
        
        for name, handler in handlers:
            if handler and handler.is_running():
                try:
                    handler.stop()
                    logger('debug', f"[GRPC-CLIENT] Handler {name} arrêté")
                except Exception as e:
                    logger('warning', f"[GRPC-CLIENT] Erreur arrêt handler {name}: {e}")
        
        # Attendre que les threads se terminent
        for name, thread in self._threads.items():
            if thread and thread.is_alive():
                thread.join(timeout=3.0)
                if thread.is_alive():
                    logger('warning', f"[GRPC-CLIENT] Thread {name} n'a pas répondu au timeout")
    
    def _close_connection(self):
        """Ferme la connexion proprement"""
        if self.connection:
            try:
                self.connection.disconnect()
                logger('debug', "[GRPC-CLIENT] Connexion fermée")
            except Exception as e:
                logger('warning', f"[GRPC-CLIENT] Erreur fermeture connexion: {e}")
            finally:
                self.connection = None
    
    def _trigger_system_shutdown(self):
        """Déclenche l'arrêt du daemon principal - VERSION CORRIGÉE"""
        try:
            # ✅ MÉTHODE 1: Callback direct via lescopr_client
            if (hasattr(self.lescopr_client, '_daemon_shutdown_callback') and 
                callable(self.lescopr_client._daemon_shutdown_callback)):
                logger('info', "[GRPC-CLIENT] Notification daemon via lescopr_client callback")
                self.lescopr_client._daemon_shutdown_callback()
                return
            
            # ✅ MÉTHODE 2: Chercher le callback dans grpc_client si différent
            if (hasattr(self.lescopr_client, 'grpc_client') and 
                self.lescopr_client.grpc_client and
                hasattr(self.lescopr_client.grpc_client, '_daemon_shutdown_callback') and
                callable(self.lescopr_client.grpc_client._daemon_shutdown_callback)):
                logger('info', "[GRPC-CLIENT] Notification daemon via grpc_client callback")
                self.lescopr_client.grpc_client._daemon_shutdown_callback()
                return
            
            # ✅ MÉTHODE 3: Signal direct au processus parent
            logger('warning', "[GRPC-CLIENT] Aucun callback trouvé, signal direct")
            parent_pid = os.getppid()
            if parent_pid > 1:
                logger('info', f"[GRPC-CLIENT] Envoi SIGTERM au daemon {parent_pid}")
                os.kill(parent_pid, signal.SIGTERM)
                return
            
            # ✅ MÉTHODE 4: Arrêt forcé
            logger('critical', "[GRPC-CLIENT] Arrêt forcé - aucune méthode de callback")
            self._force_exit()
            
        except Exception as e:
            logger('error', f"[GRPC-CLIENT] Erreur shutdown système: {e}")
            self._force_exit()
    
    def _force_exit(self):
        """Arrêt forcé en cas d'échec des méthodes propres"""
        logger('critical', "[GRPC-CLIENT] 🆘 Arrêt forcé")
        os._exit(1)
    
    def disconnect(self):
        """Ferme proprement le client gRPC"""
        try:
            self._shutdown_event.set()
            self._stop_handlers_gracefully()
            self._close_connection()
            
            logger('info', "[GRPC-CLIENT] ✅ Client déconnecté")
        except Exception as e:
            logger('warning', f"[GRPC-CLIENT] Erreur lors de la déconnexion: {e}")
    
    def is_connected(self) -> bool:
        """Vérifie si la connexion est active et le client opérationnel"""
        if self.daemon_mode:
            return (not self._shutdown_requested and
                    not self._shutdown_event.is_set() and
                    getattr(self, '_connected_simulation', False))
        else:
            return (not self._shutdown_requested and
                    not self._shutdown_event.is_set() and
                    self.connection and 
                    self.connection.is_connected())
    
    def is_running(self) -> bool:
        """Vérifie si le client est en fonctionnement"""
        return not self._shutdown_event.is_set() and not self._shutdown_requested
    
    def should_shutdown(self) -> bool:
        """
        Indique si un shutdown est demandé
        
        Utilisé par le daemon principal pour vérifier l'état
        """
        return (self._shutdown_requested or 
                (self.connection and self.connection.should_shutdown()))

    def get_connection_stats(self) -> Dict[str, Any]:
        """Retourne les statistiques détaillées"""
        base_stats = {
            'client_running': self.is_running(),
            'client_connected': self.is_connected(),
            'shutdown_requested': self._shutdown_requested,
            'active_threads': len([t for t in self._threads.values() if t.is_alive()])
        }
        
        if self.connection:
            connection_stats = self.connection.get_stats()
            return {**base_stats, **connection_stats}
        
        return base_stats
    
    def _load_connection_config(self, host: str = None, port: int = None) -> Dict[str, Any]:
        """Charge la configuration de connexion avec validation"""
        final_host = host or getattr(self.lescopr_client, 'grpc_host', self.DEFAULT_HOST)
        final_port = port or getattr(self.lescopr_client, 'grpc_port', self.DEFAULT_PORT)
        
        # ✅ VALIDATION BASIQUE
        if not isinstance(final_port, int) or final_port <= 0:
            raise ValueError(f"Port invalide: {final_port}")
        
        if not final_host or not isinstance(final_host, str):
            raise ValueError(f"Host invalide: {final_host}")
        
        return {
            'host': final_host,
            'port': final_port
        }
    
    def _initialize_handlers(self):
        """Initialise les handlers avec vérification de connexion"""
        if not self.connection or not self.connection.is_connected():
            raise GrpcConnectionError("Connexion requise pour initialiser les handlers")
        
        try:
            # ✅ CRÉER HANDLERS AVEC RÉFÉRENCE AU CLIENT
            self.control_handler = ControlChannelHandler(self)
            self.log_handler = LogHandler(self)  # Mode gRPC normal
            
            logger('debug', "[GRPC] Handlers initialisés avec shutdown support")
            
        except Exception as e:
            logger('error', f"[GRPC] Erreur initialisation handlers: {e}")
            raise

    def _initialize_daemon_handlers(self):
        """Initialise les handlers en mode daemon (pas de vraie connexion)"""
        try:
            self.control_handler = ControlChannelHandler(self)
            self.log_handler = DaemonLogHandler(self)  # ✅ MODE DAEMON

            self._start_daemon_threads()
            
            logger('debug', "[GRPC] Handlers daemon initialisés")
            
        except Exception as e:
            logger('error', f"[GRPC] Erreur initialisation handlers daemon: {e}")
            raise
    
    def _start_worker_threads(self):
        """Démarre les threads de travail avec noms descriptifs"""
        handlers = [
            ('control', self.control_handler),
            ('log', self.log_handler)
        ]
        
        started_count = 0
        for name, handler in handlers:
            if handler:
                thread = threading.Thread(
                    target=self._safe_handler_run,
                    args=(handler, name),
                    name=f"GRPC-{name.title()}",
                    daemon=True
                )
                thread.start()
                self._threads[name] = thread
                started_count += 1
        
        logger('debug', f"[GRPC] {started_count} threads démarrés avec shutdown monitoring")

    def _start_daemon_threads(self):
        """Démarre les threads pour les handlers daemon"""
        handlers = [
            ('log', self.log_handler)  # Seulement log_handler pour daemon
        ]
        
        for name, handler in handlers:
            if handler:
                thread = threading.Thread(
                    target=self._safe_handler_run,
                    args=(handler, name),
                    name=f"Daemon-{name.title()}",
                    daemon=True
                )
                thread.start()
                self._threads[name] = thread
                logger('debug', f"[GRPC] Thread daemon {name} démarré")
    
    def _safe_handler_run(self, handler, handler_name: str):
        """
        Exécute un handler avec gestion d'erreurs
        
        Évite que l'erreur d'un handler plante tout le client
        """
        try:
            logger('debug', f"[GRPC] Handler {handler_name} démarré")
            handler.run()
        except Exception as e:
            logger('error', f"[GRPC] Erreur critique handler {handler_name}: {e}")
            # Ne pas déclencher shutdown pour une erreur de handler
        finally:
            logger('debug', f"[GRPC] Handler {handler_name} terminé")
    
    def _cleanup_on_error(self):
        """Nettoie les ressources en cas d'erreur d'initialisation"""
        try:
            self._shutdown_event.set()
            
            if self.connection:
                self.connection.disconnect()
                self.connection = None
                
        except Exception as e:
            logger('warning', f"[GRPC] Erreur cleanup: {e}")
        finally:
            self.control_handler = None
            self.log_handler = None
            self._threads.clear()

    def _simulate_daemon_connection(self):
        """Simule une connexion pour l'auto-discovery (utilise daemon existant)"""
        try:
            logger('info', "[GRPC] Mode daemon attachment - connexion simulée")
            
            self._connected_simulation = True            
            self._initialize_daemon_handlers()
            
            logger('info', "[GRPC] Handlers daemon attachment créés")
        except Exception as e:
            logger('error', f"[GRPC] Erreur simulation daemon: {e}")

    @property
    def stub(self):
        """Retourne le stub gRPC pour accès direct (avec vérification)"""
        if self.connection and not self._shutdown_requested:
            return self.connection.stub
        return None