"""
Gestion des connexions gRPC de bas niveau - VERSION REFACTORISÉE
"""
import os
import json
import grpc
import threading
from typing import Optional, Callable
from datetime import datetime

from .state import ConnectionState
from .exceptions import GrpcConnectionError
from ...monitoring.logger import logger
from ...protos import sdk_service_pb2_grpc


class GrpcConnection:
    """
    Gestion de la connexion gRPC de bas niveau
    
    Responsabilités STRICTES:
    - Établissement/fermeture de connexion
    - État de connexion thread-safe
    - PAS DE LOGIQUE DE RETRY NI DE RECONNEXION AUTOMATIQUE
    """
    CONNECTION_TIMEOUT = 30

    def __init__(self, daemon_mode: bool = False):
        self._channel: Optional[grpc.Channel] = None
        self._stub: Optional[sdk_service_pb2_grpc.SDKServiceStub] = None
        self.daemon_mode = daemon_mode
        self.socket_path = '/tmp/lescopr_daemon.sock'
        self.file_queue = '/tmp/lescopr_logs_queue.json'

        self._state = ConnectionState.DISCONNECTED
        self._state_lock = threading.RLock()
        self._connected = False
        
        self._last_host: Optional[str] = None
        self._last_port: Optional[int] = None
        self._shutdown_callback: Optional[Callable[[], None]] = None

        self._stats = {
            'connection_attempts': 0,
            'successful_connections': 0,
            'failed_connections': 0,
            'last_connected': None,
            'last_error': None
        }
    
    def connect(self, host: str = None, port: int = None, 
               shutdown_callback: Optional[Callable[[], None]] = None) -> bool:
        """Établit une connexion selon le mode (gRPC direct ou daemon)"""
        self._shutdown_callback = shutdown_callback
        
        if self.daemon_mode:
            return self._connect_to_daemon()
        else:
            return self._attempt_connection(host, port)
    
    def _connect_to_daemon(self) -> bool:
        """Se connecte au daemon existant via socket/file"""
        try:
            logger('debug', "[GRPC-CONN] Début connexion daemon...")
            self._set_state(ConnectionState.CONNECTING)
            self._stats['connection_attempts'] += 1
            
            logger('debug', f"[GRPC-CONN] Test socket: {self.socket_path}")
            if os.path.exists(self.socket_path):
                logger('debug', "[GRPC-CONN] Socket existe, test connexion...")
                if self._test_daemon_socket():
                    self._connected = True
                    self._set_state(ConnectionState.CONNECTED)
                    self._stats['successful_connections'] += 1
                    self._stats['last_connected'] = datetime.now()
                    logger('success', "[GRPC-CONN] ✅ Connecté au daemon via socket")
                    return True
                else:
                    logger('warning', "[GRPC-CONN] Socket existe mais connexion échoue")
            else:
                logger('debug', "[GRPC-CONN] Socket n'existe pas")
            
            queue_dir = os.path.dirname(self.file_queue)
            logger('debug', f"[GRPC-CONN] Test file queue dir: {queue_dir}")
            if os.path.exists(queue_dir) and os.access(queue_dir, os.W_OK):
                self._connected = True
                self._set_state(ConnectionState.CONNECTED)
                self._stats['successful_connections'] += 1
                self._stats['last_connected'] = datetime.now()
                logger('success', "[GRPC-CONN] ✅ Connecté au daemon via file queue")
                return True
            else:
                logger('warning', f"[GRPC-CONN] File queue dir inaccessible: exists={os.path.exists(queue_dir)}, writable={os.access(queue_dir, os.W_OK) if os.path.exists(queue_dir) else False}")
            
            # Échec
            self._stats['failed_connections'] += 1
            self._stats['last_error'] = "Daemon non accessible"
            self._set_state(ConnectionState.FAILED)
            logger('error', "[GRPC-CONN] ❌ Aucune méthode de connexion daemon disponible")
            return False
            
        except Exception as e:
            self._stats['failed_connections'] += 1
            self._stats['last_error'] = str(e)
            self._set_state(ConnectionState.FAILED)
            logger('error', f"[GRPC-CONN] ❌ Exception connexion daemon: {e}")
            import traceback
            logger('error', f"[GRPC-CONN] Traceback: {traceback.format_exc()}")
            return False
    
    # Dans connection.py - AJOUTER DEBUG au test socket
    def _test_daemon_socket(self) -> bool:
        """Test rapide de connexion socket daemon"""
        try:
            import socket
            logger('debug', f"[GRPC-CONN] Tentative connexion socket {self.socket_path}")
            
            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            sock.settimeout(1.0)
            sock.connect(self.socket_path)
            sock.close()
            
            logger('debug', "[GRPC-CONN] ✅ Test socket réussi")
            return True
            
        except Exception as e:
            logger('debug', f"[GRPC-CONN] ❌ Test socket échoué: {e}")
            return False
    
    def send_log_to_daemon(self, log_data: dict) -> bool:
        """Envoie un log au daemon (mode daemon uniquement)"""
        if not self.daemon_mode or not self.is_connected():
            return False
        
        if self._send_via_socket(log_data):
            return True
        
        return self._send_via_file(log_data)
    
    def _send_via_socket(self, log_data: dict) -> bool:
        """Envoi via socket Unix vers les listeners du daemon"""
        try:
            import socket
            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            sock.settimeout(2.0)
            sock.connect(self.socket_path)
            
            data = json.dumps(log_data).encode('utf-8')
            sock.send(data)
            sock.close()
            
            return True
            
        except Exception:
            return False
    
    def _send_via_file(self, log_data: dict) -> bool:
        """Envoi via file queue vers les listeners du daemon"""
        try:
            import json
            with open(self.file_queue, 'a') as f:
                f.write(json.dumps(log_data) + '\n')
            return True
        except Exception:
            return False
    
    def _attempt_connection(self, host: str, port: int) -> bool:
        """Tentative de connexion simple et propre"""
        self._set_state(ConnectionState.CONNECTING)
        self._stats['connection_attempts'] += 1
        
        self._last_host = host
        self._last_port = port
        
        try:
            self._cleanup()
            
            target = f"{host}:{port}"
            self._channel = grpc.insecure_channel(target, options=self._get_channel_options())
            
            grpc.channel_ready_future(self._channel).result(timeout=self.CONNECTION_TIMEOUT)
            
            self._stub = sdk_service_pb2_grpc.SDKServiceStub(self._channel)
            
            if not self._stub:
                raise GrpcConnectionError("Échec création stub")
            
            self._set_state(ConnectionState.CONNECTED)
            self._connected = True
            self._stats['successful_connections'] += 1
            self._stats['last_connected'] = datetime.now()
            self._stats['last_error'] = None
            
            logger('info', f"[GRPC-CONN] ✅ Connecté à {target}")
            return True
            
        except Exception as e:
            self._stats['failed_connections'] += 1
            self._stats['last_error'] = str(e)
            self._connected = False
            self._set_state(ConnectionState.FAILED)
            self._cleanup()
            
            logger('error', f"[GRPC-CONN] ❌ Échec connexion: {e}")
            return False

    def _handle_connection_failure(self, error_msg: str):
        """Gère les échecs de connexion signalés par les handlers"""
        with self._state_lock:
            if not hasattr(self, '_consecutive_failures'):
                self._consecutive_failures = 0
            
            self._consecutive_failures += 1
            self._stats['failed_connections'] += 1
            self._stats['last_error'] = error_msg
            
            logger('critical', f"[GRPC-CONN] ÉCHEC #{self._consecutive_failures}: {error_msg}")
            
            if self._consecutive_failures >= 5:
                logger('critical', f"[GRPC-CONN] 🛑 SHUTDOWN DÉCLENCHÉ: Limite de 5 échecs atteinte")
                self.trigger_shutdown(f"Limite de 5 échecs atteinte")
            else:
                self._connected = False
                self._set_state(ConnectionState.FAILED)
                self._cleanup()    

    def trigger_shutdown(self, reason: str):
        """Déclenche le shutdown du daemon"""
        logger('critical', f"[GRPC-CONN] 🛑 SHUTDOWN DÉCLENCHÉ: {reason}")
        
        if self._shutdown_callback:
            try:
                self._shutdown_callback()
            except Exception as e:
                logger('error', f"[GRPC-CONN] Erreur shutdown callback: {e}")
        else:
            logger('warning', "[GRPC-CONN] Aucun callback shutdown configuré")
    
    def disconnect(self):
        """Ferme proprement la connexion"""
        with self._state_lock:
            if self._state != ConnectionState.DISCONNECTED:
                logger('debug', "[GRPC-CONN] Déconnexion en cours...")
                self._connected = False
                self._set_state(ConnectionState.DISCONNECTED)
                self._cleanup()
                logger('info', "[GRPC-CONN] ✅ Déconnecté")
    
    def is_connected(self) -> bool:
        """Vérifie l'état de connexion selon le mode"""
        with self._state_lock:
            if self.daemon_mode:
                return (self._state == ConnectionState.CONNECTED and 
                       self._connected)
            else:
                return (self._state == ConnectionState.CONNECTED and 
                       self._connected and 
                       self._channel is not None and 
                       self._stub is not None)
    
    def get_stats(self) -> dict:
        """Retourne les statistiques selon le mode"""
        with self._state_lock:
            base_stats = {
                'connection_state': self._state.value,
                'connected': self._connected,
                'daemon_mode': self.daemon_mode,
                **self._stats
            }
            
            if self.daemon_mode:
                base_stats.update({
                    'socket_available': os.path.exists(self.socket_path),
                    'file_queue_dir_writable': os.access(
                        os.path.dirname(self.file_queue), os.W_OK
                    ) if os.path.exists(os.path.dirname(self.file_queue)) else False
                })
            else:
                base_stats.update({
                    'has_channel': self._channel is not None,
                    'has_stub': self._stub is not None,
                    'last_host': self._last_host,
                    'last_port': self._last_port
                })
            
            return base_stats
    
    def _cleanup(self):
        """Nettoie les ressources gRPC"""
        if self._channel:
            try:
                self._channel.close()
            except Exception:
                pass
        self._channel = None
        self._stub = None
    
    def _set_state(self, new_state: ConnectionState):
        """Met à jour l'état de façon thread-safe"""
        if self._state != new_state:
            old_state = self._state
            self._state = new_state
            logger('debug', f"[GRPC-CONN] {old_state.value} → {new_state.value}")
    
    def _get_channel_options(self):
        """Options optimisées pour éviter too_many_pings"""
        return [
            ('grpc.keepalive_time_ms', 60000),
            ('grpc.keepalive_timeout_ms', 10000),
            ('grpc.keepalive_permit_without_calls', False),
            ('grpc.http2.max_pings_without_data', 1),
            ('grpc.http2.min_time_between_pings_ms', 30000),
            ('grpc.http2.min_ping_interval_without_data_ms', 60000),
            ('grpc.http2.max_ping_strikes', 2),
            ('grpc.max_connection_idle_ms', 300000),
        ]
    
    @property
    def stub(self) -> Optional[sdk_service_pb2_grpc.SDKServiceStub]:
        """Retourne le stub gRPC"""
        return self._stub
    
    @property
    def state(self) -> ConnectionState:
        """Retourne l'état actuel"""
        with self._state_lock:
            return self._state
    
    @property
    def channel(self) -> Optional[grpc.Channel]:
        """Retourne le canal gRPC"""
        return self._channel