"""
Handler pour le canal de contrôle bidirectionnel - VERSION FINAL CLEAN
Responsabilités: Init SDK + Métriques système UNIQUEMENT
"""
import time
import json
import grpc
from datetime import datetime
from typing import Optional, Generator

from .base import BaseHandler
from ..exceptions import GrpcConnectionError
from ....monitoring.logger import logger
from ....monitoring.metrics import (
    get_cpu_usage, get_ram_usage, get_disk_usage, get_disk_percent, get_network_bytes_recv,
    get_process_count, get_uptime, get_network_bytes_sent,
    get_ram_percent
)
from ....protos import sdk_service_pb2


class ControlChannelHandler(BaseHandler):
    """
    Handler pour le canal de contrôle bidirectionnel
    
    PRINCIPES SIMPLIFIÉS:
    - Fait son travail si connecté
    - Signale IMMÉDIATEMENT les échecs à GrpcConnection  
    - N'essaie JAMAIS de gérer la reconnexion
    - Sort de la boucle dès qu'il ne peut plus travailler
    """
    
    STREAM_SLEEP_INTERVAL = 5.0
    METRICS_INTERVAL = 600
    MAX_RECONNECT_ATTEMPTS = 5
    RECONNECT_DELAY = 10
    
    def __init__(self, grpc_client):
        super().__init__(grpc_client, "ControlChannel")
        
        self.init_confirmed = False
        self._init_sent = False
        
        self._last_metrics = 0
        self._consecutive_failures = 0
        self._last_connection_attempt = 0
    
    def run(self):
        """Boucle principale avec gestion complète reconnexion"""
        self.start()
        logger('info', f"[{self.name}] Démarrage avec gestion reconnexion intégrée")
        
        while self.is_running():
            try:
                if not self.is_connected():
                    if not self._handle_disconnection():
                        break
                    continue
                
                success = self._attempt_work_simple()
                
                if success:
                    self._consecutive_failures = 0 
                    time.sleep(self.STREAM_SLEEP_INTERVAL)
                else:
                    time.sleep(1)
                    
            except grpc.RpcError as e:
                if "Cancelling all calls" in str(e) or "UNAVAILABLE" in str(e):
                    logger('warning', f"[{self.name}] Serveur indisponible")
                else:
                    logger('error', f"[{self.name}] Erreur gRPC: {e}")
                self._consecutive_failures += 1
                
                if hasattr(self.grpc_client.connection, '_connected'):
                    self.grpc_client.connection._connected = False
                
            except Exception as e:
                logger('error', f"[{self.name}] Erreur générale: {e}")
                time.sleep(2)
        
        logger('info', f"[{self.name}] Arrêt du handler")
        self.stop()

    
    def _handle_disconnection(self) -> bool:
        """
        Gère la déconnexion avec tentatives de reconnexion
        
        Returns:
            bool: True si reconnecté, False si échec final
        """
        current_time = time.time()
        
        if current_time - self._last_connection_attempt < self.RECONNECT_DELAY:
            time.sleep(2)
            return True
        
        self._last_connection_attempt = current_time
        self._consecutive_failures += 1
        
        logger('warning', f"[{self.name}] Tentative reconnexion {self._consecutive_failures}/{self.MAX_RECONNECT_ATTEMPTS}")
        
        connection_success = self.grpc_client.connection._attempt_connection(
            self.grpc_client.connection._last_host,
            self.grpc_client.connection._last_port
        )
        
        if connection_success:
            logger('info', f"[{self.name}] ✅ Reconnexion réussie")
            self._consecutive_failures = 0
            self.init_confirmed = False
            self._init_sent = False
            return True
        
        if self._consecutive_failures >= self.MAX_RECONNECT_ATTEMPTS:
            logger('critical', f"[{self.name}] 🛑 Limite de {self.MAX_RECONNECT_ATTEMPTS} tentatives atteinte")
            self._trigger_final_shutdown()
            return False
        
        logger('warning', f"[{self.name}] ❌ Échec tentative {self._consecutive_failures}, retry dans {self.RECONNECT_DELAY}s")
        return True
    
    def _trigger_final_shutdown(self):
        """Déclenche le shutdown final du daemon"""
        reason = f"Échec reconnexion après {self.MAX_RECONNECT_ATTEMPTS} tentatives"
        
        if (self.grpc_client and 
            self.grpc_client.connection and 
            hasattr(self.grpc_client.connection, 'trigger_shutdown')):
            
            self.grpc_client.connection.trigger_shutdown(reason)
        else:
            logger('error', f"[{self.name}] Impossible de déclencher shutdown")

    
    def _attempt_work_simple(self) -> bool:
        """
        Tentative de travail SANS capture d'erreur gRPC
        
        Laisse les erreurs gRPC remonter vers run() pour signalement
        """
        try:
            # ✅ VALIDATION SDK ID EN AMONT
            if not self._ensure_sdk_id_available():
                logger('debug', f"[{self.name}] SDK ID non disponible")
                return False
            
            # ✅ OUVERTURE CANAL - ERREURS gRPC REMONTENT
            responses = self._open_control_channel_direct()
            if not responses:
                return False
            
            # ✅ TRAITEMENT RÉPONSES - ERREURS gRPC REMONTENT  
            self._process_responses_direct(responses)
            return True
            
        except grpc.RpcError:
            # ✅ LAISSER REMONTER VERS run()
            raise
        except GrpcConnectionError:
            # ✅ LAISSER REMONTER VERS run()
            raise
        except Exception as e:
            # ✅ AUTRES ERREURS = ÉCHEC DE TRAVAIL
            logger('debug', f"[{self.name}] Échec travail non-gRPC: {e}")
            return False
    
    def _ensure_sdk_id_available(self) -> bool:
        """Vérifie que le SDK ID est disponible"""
        sdk_id = self._get_validated_sdk_id()
        if not sdk_id:
            logger('debug', f"[{self.name}] SDK ID manquant ou invalide")
            return False
        return True
    
    def _open_control_channel_direct(self) -> Optional[grpc.Call]:
        """Ouvre le canal SANS gestion d'erreur - laisse tout remonter"""
        if not self.stub:
            raise GrpcConnectionError("Stub non disponible")
        
        logger('debug', f"[{self.name}] Ouverture canal de contrôle...")
        
        # ✅ APPEL DIRECT - ERREURS gRPC REMONTENT NATURELLEMENT
        responses = self.stub.ControlChannel(self._generate_commands_direct())
        
        logger('debug', f"[{self.name}] Canal de contrôle ouvert")
        return responses
    
    def _generate_commands_direct(self) -> Generator:
        """Générateur de commandes SANS gestion d'erreur"""
        while self.is_running():
            current_time = time.time()
            commands_to_send = []
            
            # Commande d'initialisation (une seule fois)
            if not self.init_confirmed and not self._init_sent:
                init_cmd = self._create_init_command_safe()
                if init_cmd:
                    commands_to_send.append(init_cmd)
                    self._init_sent = True
    
            log_cmd = self._create_logs_command_if_available()
            if log_cmd:
                commands_to_send.append(log_cmd)
            
            # Métriques système périodiques
            if current_time - self._last_metrics >= self.METRICS_INTERVAL:
                metrics_cmd = self._create_metrics_command_safe()
                if metrics_cmd:
                    commands_to_send.append(metrics_cmd)
                    self._last_metrics = current_time
            
            # Envoyer les commandes
            for cmd in commands_to_send:
                yield cmd
                time.sleep(0.1)
            
            time.sleep(self.STREAM_SLEEP_INTERVAL)
    
    def _create_init_command_safe(self) -> Optional[sdk_service_pb2.ControlCommand]:
        """Crée la commande d'initialisation AVEC validation"""
        try:
            sdk_id = self._get_validated_sdk_id()
            if not sdk_id:
                logger('warning', f"[{self.name}] SDK ID non disponible pour init")
                return None
            
            from google.protobuf.struct_pb2 import Struct
            
            params = Struct()
            params.update({
                "client_version": "1.0.0",
                "timestamp": datetime.now().isoformat(),
                "sdk_id": sdk_id,
                "message": "Control channel initiated by SDK"
            })
            
            return sdk_service_pb2.ControlCommand(
                command_type="sdk_init",
                target="server",
                parameters=params
            )
            
        except Exception as e:
            logger('error', f"[{self.name}] Erreur création commande init: {e}")
            return None
    
    def _create_metrics_command_safe(self) -> Optional[sdk_service_pb2.ControlCommand]:
        """Crée la commande métriques AVEC validation"""
        try:
            sdk_id = self._get_validated_sdk_id()
            if not sdk_id:
                logger('warning', f"[{self.name}] SDK ID non disponible pour métriques")
                return None
            
            current_timestamp = datetime.now().isoformat()
            
            # Collecte métriques avec validation
            metrics_data = {}
            metrics_functions = [
                ('cpu_usage', get_cpu_usage),
                ('ram_usage', get_ram_usage),
                ('ram_percent', get_ram_percent),
                ('disk_usage', get_disk_usage),
                ('disk_percent', get_disk_percent),
                ('network_sent', get_network_bytes_sent),
                ('network_recv', get_network_bytes_recv),
                ('process_count', get_process_count),
                ('uptime', get_uptime)
            ]
            
            for name, func in metrics_functions:
                try:
                    value = func()
                    if value is not None:
                        metrics_data[name] = float(value)
                except Exception as e:
                    logger('debug', f"[{self.name}] Erreur métrique {name}: {e}")
            
            if not metrics_data:
                logger('warning', f"[{self.name}] Aucune métrique collectée")
                return None
            
            metrics_list = [
                {
                    "name": name,
                    "value": value,
                    "unit": "percent" if "percent" in name else "bytes" if "usage" in name else "count"
                }
                for name, value in metrics_data.items()
            ]
            
            string_params = {
                "timestamp": current_timestamp,
                "sdk_id": str(sdk_id),
                "metrics_batch": json.dumps(metrics_list)
            }

            logger('debug', f"[{self.name}] Métriques créées: {len(metrics_list)} entrées")
            
            return sdk_service_pb2.ControlCommand(
                command_type="sdk_metrics",
                target="server",
                parameters=string_params
            )
            
        except Exception as e:
            logger('error', f"[{self.name}] Erreur création métriques: {e}")
            return None
    
    def _create_logs_command_if_available(self) -> Optional[sdk_service_pb2.ControlCommand]:
        try:
            if hasattr(self.grpc_client, 'lescopr_client'):
                lescopr_client = self.grpc_client.lescopr_client
                
                if hasattr(lescopr_client, 'has_pending_logs') and lescopr_client.has_pending_logs():
                    pending_logs = lescopr_client.get_pending_logs()
                    #print(f"🔴 DEBUG: {len(pending_logs)} logs trouvés dans la queue")
                    
                    if pending_logs:
                        sdk_id = self._get_validated_sdk_id()
                        if not sdk_id:
                            return None
                        
                        command = sdk_service_pb2.ControlCommand(
                            command_type="sdk_logs",
                            target="server",
                            parameters={
                                "sdk_id": sdk_id,
                                "logs_batch": json.dumps(pending_logs),
                                "timestamp": datetime.now().isoformat()
                            }
                        )
                        
                        #print(f"🔴 DEBUG: Commande sdk_logs créée pour {len(pending_logs)} logs")
                        return command
            
            return None
        except Exception as e:
            print(f"🔴 DEBUG: Erreur création commande logs: {e}")
            return None


    def _process_responses_direct(self, responses):
        """Traite les réponses SANS capture d'erreur gRPC"""
        for response in responses:
            # ✅ TRAITEMENT SIMPLE - ERREURS gRPC REMONTENT
            if hasattr(response, 'success') and response.success:
                self._process_successful_response(response)
            
            time.sleep(0.1)
    
    def _process_successful_response(self, response):
        """Traite une réponse réussie"""
        if self._is_initialization_ack(response):
            self.init_confirmed = True
            logger('info', f"[{self.name}] Initialisation confirmée par le serveur")
    
    def _is_initialization_ack(self, response) -> bool:
        """Vérifie si c'est un accusé de réception d'initialisation"""
        try:
            data = response.data if hasattr(response, 'data') else {}
            is_server_ack = data.get("response_type") == "server_ack_init_control"
            is_success_ack = response.success and "Accusé de réception" in response.message
            is_ready_status = response.success and data.get("server_status") == "ready"
            return is_server_ack or is_success_ack or is_ready_status
        except Exception:
            return False
    
    def _get_validated_sdk_id(self) -> Optional[str]:
        """Récupère et valide le SDK ID"""
        try:
            if not hasattr(self.grpc_client, 'lescopr_client'):
                return None
                
            sdk_id = getattr(self.grpc_client.lescopr_client, 'sdk_id', None)
            if not sdk_id or sdk_id == "unknown_sdk_id":
                return None
            return str(sdk_id)
        except Exception:
            return None
    
    def get_stats(self) -> dict:
        """Retourne les statistiques du handler"""
        stats = super().get_stats()
        stats.update({
            'init_confirmed': self.init_confirmed,
            'init_sent': self._init_sent,
            'consecutive_failures': self._consecutive_failures,
            'last_metrics_send': self._last_metrics
        })
        return stats