#!/usr/bin/env python3
"""Socket.IO connection pool for Claude Code hook handler.

This module provides connection pooling for Socket.IO clients to reduce
connection overhead and implement circuit breaker patterns.
"""

import time
from typing import Any, Dict, List, Optional

# Import constants for configuration
try:
    from claude_mpm.core.constants import NetworkConfig
except ImportError:
    # Fallback values if constants module not available
    class NetworkConfig:
        SOCKETIO_PORT_RANGE = (8080, 8099)
        RECONNECTION_DELAY = 0.5
        SOCKET_WAIT_TIMEOUT = 1.0


# Socket.IO import
try:
    import socketio

    SOCKETIO_AVAILABLE = True
except ImportError:
    SOCKETIO_AVAILABLE = False
    socketio = None


class SocketIOConnectionPool:
    """Connection pool for Socket.IO clients to prevent connection leaks."""

    def __init__(self, max_connections: int = 3):
        self.max_connections = max_connections
        self.connections: List[Dict[str, Any]] = []
        self.last_cleanup = time.time()

    def get_connection(self, port: int) -> Optional[Any]:
        """Get or create a connection to the specified port."""
        if time.time() - self.last_cleanup > 60:
            self._cleanup_dead_connections()
            self.last_cleanup = time.time()

        for conn in self.connections:
            if conn.get("port") == port and conn.get("client"):
                client = conn["client"]
                if self._is_connection_alive(client):
                    return client
                else:
                    self.connections.remove(conn)

        if len(self.connections) < self.max_connections:
            client = self._create_connection(port)
            if client:
                self.connections.append(
                    {"port": port, "client": client, "created": time.time()}
                )
                return client

        if self.connections:
            oldest = min(self.connections, key=lambda x: x["created"])
            self._close_connection(oldest["client"])
            oldest["client"] = self._create_connection(port)
            oldest["port"] = port
            oldest["created"] = time.time()
            return oldest["client"]

        return None

    def _create_connection(self, port: int) -> Optional[Any]:
        """Create a new Socket.IO connection with persistent keep-alive.
        
        WHY persistent connections:
        - Maintains connection throughout handler lifecycle
        - Automatic reconnection on disconnect
        - Reduced connection overhead for multiple events
        - Better reliability for event delivery
        """
        if not SOCKETIO_AVAILABLE:
            return None
        try:
            client = socketio.Client(
                reconnection=True,  # Enable automatic reconnection
                reconnection_attempts=5,  # Try to reconnect up to 5 times
                reconnection_delay=0.5,  # Wait 0.5s between reconnection attempts
                reconnection_delay_max=2.0,  # Max delay between attempts
                logger=False,
                engineio_logger=False,
            )
            
            # Set up event handlers for connection lifecycle
            @client.on('connect')
            def on_connect():
                pass  # Connection established
            
            @client.on('disconnect')
            def on_disconnect():
                pass  # Will automatically try to reconnect
            
            client.connect(
                f"http://localhost:{port}",
                wait=True,  # Wait for connection to establish
                wait_timeout=NetworkConfig.SOCKET_WAIT_TIMEOUT,
                transports=['websocket', 'polling'],  # Try WebSocket first, fall back to polling
            )
            
            if client.connected:
                # Send a keep-alive ping to establish the connection
                try:
                    client.emit('ping', {'timestamp': time.time()})
                except:
                    pass  # Ignore ping errors
                return client
        except Exception:
            pass
        return None

    def _is_connection_alive(self, client: Any) -> bool:
        """Check if a connection is still alive.
        
        WHY enhanced check:
        - Verifies actual connection state
        - Attempts to ping server for liveness check
        - More reliable than just checking connected flag
        """
        try:
            if not client:
                return False
            
            # Check basic connection state
            if not client.connected:
                return False
            
            # Try a quick ping to verify connection is truly alive
            # This helps detect zombie connections
            try:
                # Just emit a ping, don't wait for response (faster)
                client.emit('ping', {'timestamp': time.time()})
                return True
            except:
                # If ping fails, connection might be dead
                return client.connected  # Fall back to basic check
        except:
            return False

    def _close_connection(self, client: Any) -> None:
        """Safely close a connection."""
        try:
            if client:
                client.disconnect()
        except:
            pass

    def _cleanup_dead_connections(self) -> None:
        """Remove dead connections from the pool and attempt reconnection.
        
        WHY proactive reconnection:
        - Maintains pool health
        - Ensures connections are ready when needed
        - Reduces latency for event emission
        """
        alive_connections = []
        for conn in self.connections:
            client = conn.get("client")
            if self._is_connection_alive(client):
                alive_connections.append(conn)
            else:
                # Try to reconnect dead connections
                self._close_connection(client)
                new_client = self._create_connection(conn.get("port", 8765))
                if new_client:
                    conn["client"] = new_client
                    conn["created"] = time.time()
                    alive_connections.append(conn)
        self.connections = alive_connections

    def close_all(self) -> None:
        """Close all connections in the pool."""
        for conn in self.connections:
            self._close_connection(conn.get("client"))
        self.connections.clear()
