import json
import threading
import os
from typing import Dict, Any, List, Optional, Callable
from .interfaces import MessageBusInterface
from .message import Message
from diona.project.storage import get_storage_instance

class MessageBus(MessageBusInterface):
    """Message bus implementation."""
    
    # Module name to instance mapping for singleton pattern
    _instances: Dict[str, 'MessageBus'] = {}
    
    def __new__(cls, module_name: str):
        if module_name not in cls._instances:
            cls._instances[module_name] = super(MessageBus, cls).__new__(cls)
            cls._instances[module_name]._initialize(module_name)
        return cls._instances[module_name]
    
    def _initialize(self, module_name: str):
        """Initialize the message bus."""
        self.module_name = module_name
        self.storage = get_storage_instance()
        self.subscribers: Dict[str, List[Callable]] = {}
        self.lock = threading.RLock()  # Reentrant lock for thread safety
    
    def publish(self, message: Message) -> bool:
        """Publish a message."""
        try:
            # Set message module if not set
            if not message.message_module:
                message.message_module = self.module_name
            
            # Get topic
            topic = message.message_topic
            
            # Save messages to files with lock
            with self.lock:
                # Save to all messages file
                all_file_path = f"{topic}_all.json"
                all_messages = self._load_messages(all_file_path)
                all_messages.append(message.to_dict())
                self._save_messages(all_file_path, all_messages)
                
                # Save to queue file
                queue_file_path = f"{topic}_queue.json"
                queue_messages = self._load_messages(queue_file_path)
                queue_messages.append(message.to_dict())
                self._save_messages(queue_file_path, queue_messages)
            
            # Notify subscribers (outside lock to avoid deadlocks)
            with self.lock:
                subscribers = self.subscribers.get(topic, [])[:]  # Create a copy
            
            for callback in subscribers:
                try:
                    callback(message)
                except Exception as e:
                    print(f"Error in subscriber callback: {e}")
            
            return True
        except Exception as e:
            print(f"Error publishing message: {e}")
            return False
    
    def subscribe(self, topic: str, callback: Callable[[Message], None]) -> bool:
        """Subscribe to a topic."""
        try:
            with self.lock:
                if topic not in self.subscribers:
                    self.subscribers[topic] = []
                if callback not in self.subscribers[topic]:
                    self.subscribers[topic].append(callback)
            return True
        except Exception as e:
            print(f"Error subscribing to topic: {e}")
            return False
    
    def unsubscribe(self, topic: str, callback: Callable[[Message], None]) -> bool:
        """Unsubscribe from a topic."""
        try:
            with self.lock:
                if topic in self.subscribers and callback in self.subscribers[topic]:
                    self.subscribers[topic].remove(callback)
            return True
        except Exception as e:
            print(f"Error unsubscribing from topic: {e}")
            return False
    
    def pull_message(self, topic: str) -> Optional[Message]:
        """Pull a message from the queue."""
        try:
            with self.lock:
                queue_file_path = f"{topic}_queue.json"
                queue_messages = self._load_messages(queue_file_path)
                
                if queue_messages:
                    # Get first message
                    message_data = queue_messages.pop(0)
                    # Save remaining messages
                    self._save_messages(queue_file_path, queue_messages)
                    # Create message object
                    return Message.from_dict(message_data)
                return None
        except Exception as e:
            print(f"Error pulling message: {e}")
            return None
    
    def pull_and_notify(self, topic: str) -> Optional[Message]:
        """Pull a message from the queue and notify subscribers."""
        message = self.pull_message(topic)
        if message:
            # Notify subscribers
            with self.lock:
                subscribers = self.subscribers.get(topic, [])[:]  # Create a copy
            
            for callback in subscribers:
                try:
                    callback(message)
                except Exception as e:
                    print(f"Error in subscriber callback: {e}")
        return message
    
    def process_queue(self, topic: str) -> int:
        """Process all messages in the queue and notify subscribers."""
        processed_count = 0
        while True:
            message = self.pull_and_notify(topic)
            if not message:
                break
            processed_count += 1
        return processed_count
    
    def get_module_name(self) -> str:
        """Get the module name."""
        return self.module_name
    
    def _load_messages(self, file_path: str) -> List[Dict[str, Any]]:
        """Load messages from file."""
        try:
            # Use module_name as part of the relative path
            relative_path = os.path.join(self.module_name, file_path)
            content = self.storage.read("bus", relative_path, storage_type="project")
            if content:
                return json.loads(content)
            return []
        except Exception:
            return []
    
    def _save_messages(self, file_path: str, messages: List[Dict[str, Any]]):
        """Save messages to file."""
        try:
            # Use module_name as part of the relative path
            relative_path = os.path.join(self.module_name, file_path)
            content = json.dumps(messages, ensure_ascii=False, indent=2)
            self.storage.write("bus", relative_path, content, storage_type="project")
        except Exception as e:
            print(f"Error saving messages: {e}")
    
    def _notify_existing_messages_for_topic(self, topic: str, callback: Callable[[Message], None]):
        """Notify a callback about existing messages in a specific topic's queue."""
        try:
            # Load messages from queue
            queue_file_path = f"{topic}_queue.json"
            queue_messages = self._load_messages(queue_file_path)
            
            # Notify callback for each message
            if queue_messages:
                for message_data in queue_messages:
                    try:
                        message = Message.from_dict(message_data)
                        try:
                            callback(message)
                        except Exception as e:
                            print(f"Error in subscriber callback: {e}")
                    except Exception as e:
                        print(f"Error creating message from data: {e}")
        except Exception as e:
            print(f"Error notifying existing messages for topic {topic}: {e}")

# Get message bus instance
def get_message_bus(module_name: str) -> MessageBus:
    """Get the message bus instance for a module."""
    return MessageBus(module_name)
