import asyncio
import importlib
import inspect
import json
import logging
import threading
from pathlib import Path
from typing import Any, Dict, List, Optional, Union, Callable, Coroutine
from fastapi import WebSocket, WebSocketDisconnect
from fastapi.websockets import WebSocketState
from .config_manager import ConfigManager

logger = logging.getLogger(__name__)

class WebSocketManager:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._init()
        return cls._instance

    def _init(self):
        self._project_root = None
        self._config = ConfigManager()
        self._connections: Dict[str, Dict[str, WebSocket]] = {}
        self._rooms: Dict[str, List[str]] = {}
        self._namespaces: Dict[str, Dict[str, Any]] = {}
        self._event_handlers: Dict[str, Dict[str, Callable]] = {}
        self._lock = threading.RLock()
        self._server = None

    def set_project_root(self, path: Path):
        """Définit la racine du projet"""
        with self._lock:
            self._project_root = path.resolve()

    async def initialize(self):
        """Initialise le serveur WebSocket et charge les handlers"""
        with self._lock:
            if not self._project_root:
                raise ValueError("Project root not set")

            # Charge les handlers globaux
            global_ws_dir = self._project_root / 'ws'
            if global_ws_dir.exists():
                self._load_handlers('global', global_ws_dir)

            # Charge les handlers des modules
            modules_metadata = self._config.get_modules_metadata()
            modules_dir = self._project_root / 'modules'
            if modules_dir.exists():
                for module_name in modules_metadata.keys():
                    module_ws_dir = modules_dir / module_name / 'ws'
                    if module_ws_dir.exists():
                        self._load_handlers(module_name, module_ws_dir)

    def _load_handlers(self, context: str, ws_dir: Path):
        """Charge les handlers WebSocket depuis un répertoire"""
        for handler_file in ws_dir.glob('**/*.py'):
            if handler_file.name.startswith(('_', '.')):
                continue

            try:
                # Construction du chemin d'import correct
                rel_path = handler_file.relative_to(self._project_root)
                module_path = '.'.join(rel_path.with_suffix('').parts)
                
                # Debug: Affiche le chemin d'import
                logger.debug(f"Importing WebSocket handler from: {module_path}")
                
                module = importlib.import_module(module_path)

                for name, obj in inspect.getmembers(module):
                    if inspect.isfunction(obj) or inspect.iscoroutinefunction(obj):
                        if name.startswith('on_'):
                            event_name = name[3:]
                            self._register_handler(context, event_name, obj)

            except Exception as e:
                logger.error(f"Failed to load WebSocket handler {handler_file}: {e}")
                logger.error(f"Import path attempted: {module_path}")
                logger.error(f"Project root: {self._project_root}")
                logger.error(f"Relative path: {rel_path}")

    def _register_handler(self, context: str, event_name: str, handler: Callable):
        """Enregistre un handler d'événement"""
        with self._lock:
            # Applique les préfixes du module
            module_config = self._config._configs.get(context, {})
            websocket_config = module_config.get('websocket', {})
            
            # Préfixe l'événement si configuré
            if 'event-prefix' in websocket_config:
                event_name = f"{websocket_config['event-prefix']}:{event_name}"
            
            # Enregistre avec le namespace approprié
            namespace = '/'
            if 'namespace-prefix' in websocket_config:
                namespace = websocket_config['namespace-prefix']
            elif 'module-prefix' in websocket_config:
                namespace = websocket_config['module-prefix']
            elif context != 'global':
                namespace = f"/{context}"

            if namespace not in self._event_handlers:
                self._event_handlers[namespace] = {}
            
            self._event_handlers[namespace][event_name] = handler
            logger.info(f"Registered WebSocket handler: {namespace}/{event_name}")

    async def handle_connection(self, websocket: WebSocket, namespace: str = '/'):
        """Gère une nouvelle connexion WebSocket"""
        await websocket.accept()
        connection_id = id(websocket)
        
        with self._lock:
            if namespace not in self._connections:
                self._connections[namespace] = {}
            self._connections[namespace][str(connection_id)] = websocket

        try:
            while True:
                data = await websocket.receive_text()
                await self._handle_message(websocket, namespace, data)
        except WebSocketDisconnect:
            await self._handle_disconnect(websocket, namespace)
        except Exception as e:
            logger.error(f"WebSocket error: {e}")
            await self._handle_disconnect(websocket, namespace)

    async def _handle_message(self, websocket: WebSocket, namespace: str, data: str):
        """Traite un message WebSocket"""
        try:
            message = json.loads(data)
            event = message.get('event')
            payload = message.get('data')
            
            if not event:
                return

            # Trouve le handler approprié
            handler = None
            with self._lock:
                if namespace in self._event_handlers and event in self._event_handlers[namespace]:
                    handler = self._event_handlers[namespace][event]

            if handler:
                try:
                    if inspect.iscoroutinefunction(handler):
                        await handler(websocket, payload)
                    else:
                        handler(websocket, payload)
                except Exception as e:
                    logger.error(f"Error in WebSocket handler {event}: {e}")
        except json.JSONDecodeError:
            logger.error("Invalid JSON received over WebSocket")

    async def _handle_disconnect(self, websocket: WebSocket, namespace: str):
        """Gère la déconnexion d'un client"""
        connection_id = id(websocket)
        
        with self._lock:
            if namespace in self._connections and str(connection_id) in self._connections[namespace]:
                del self._connections[namespace][str(connection_id)]

        # Quitte toutes les rooms
        for room in list(self._rooms.keys()):
            self.leave_room(room, websocket)

    async def emit(self, event: str, data: Any, namespace: str = '/', room: str = None):
        """Émet un événement à des clients spécifiques"""
        message = json.dumps({'event': event, 'data': data})
        
        with self._lock:
            if room:
                # Émission à une room spécifique
                if room in self._rooms:
                    for connection_id in self._rooms[room]:
                        for ns in [namespace, '/']:  # Essaye le namespace spécifié puis le global
                            if ns in self._connections and connection_id in self._connections[ns]:
                                try:
                                    websocket = self._connections[ns][connection_id]
                                    if websocket.client_state == WebSocketState.CONNECTED:
                                        await websocket.send_text(message)
                                except Exception as e:
                                    logger.error(f"Error emitting to {connection_id}: {e}")
            else:
                # Émission à tous les clients du namespace
                for ns in [namespace, '/']:  # Essaye le namespace spécifié puis le global
                    if ns in self._connections:
                        for connection_id, websocket in self._connections[ns].items():
                            try:
                                if websocket.client_state == WebSocketState.CONNECTED:
                                    await websocket.send_text(message)
                            except Exception as e:
                                logger.error(f"Error emitting to {connection_id}: {e}")

    def join_room(self, room: str, websocket: WebSocket, namespace: str = '/'):
        """Ajoute un client à une room"""
        connection_id = str(id(websocket))
        
        with self._lock:
            # Applique le préfixe de room si configuré
            module_config = self._get_module_config_by_namespace(namespace)
            if module_config and 'room-prefix' in module_config.get('websocket', {}):
                room = f"{module_config['websocket']['room-prefix']}:{room}"

            if room not in self._rooms:
                self._rooms[room] = []
            
            if connection_id not in self._rooms[room]:
                self._rooms[room].append(connection_id)

    def leave_room(self, room: str, websocket: WebSocket, namespace: str = '/'):
        """Retire un client d'une room"""
        connection_id = str(id(websocket))
        
        with self._lock:
            # Applique le préfixe de room si configuré
            module_config = self._get_module_config_by_namespace(namespace)
            if module_config and 'room-prefix' in module_config.get('websocket', {}):
                room = f"{module_config['websocket']['room-prefix']}:{room}"

            if room in self._rooms and connection_id in self._rooms[room]:
                self._rooms[room].remove(connection_id)
                if not self._rooms[room]:  # Supprime la room si vide
                    del self._rooms[room]

    def _get_module_config_by_namespace(self, namespace: str) -> Optional[Dict]:
        """Trouve la configuration du module correspondant à un namespace"""
        with self._lock:
            for module_name, config in self._config._configs.items():
                websocket_config = config.get('websocket', {})
                if 'namespace-prefix' in websocket_config and websocket_config['namespace-prefix'] == namespace:
                    return config
                if 'module-prefix' in websocket_config and websocket_config['module-prefix'] == namespace:
                    return config
                if module_name != 'global' and namespace == f"/{module_name}":
                    return config
            return None

    async def broadcast(self, event: str, data: Any, namespace: str = None):
        """Diffuse un événement à tous les clients connectés"""
        namespaces = [namespace] if namespace else list(self._connections.keys())
        
        for ns in namespaces:
            await self.emit(event, data, ns)

    def get_connected_clients(self, namespace: str = '/') -> List[WebSocket]:
        """Retourne la liste des clients connectés à un namespace"""
        with self._lock:
            return list(self._connections.get(namespace, {}).values())

    def get_rooms(self) -> Dict[str, List[str]]:
        """Retourne la liste des rooms et leurs membres"""
        with self._lock:
            return self._rooms.copy()

    def get_namespaces(self) -> List[str]:
        """Retourne la liste des namespaces actifs"""
        with self._lock:
            return list(self._connections.keys())