from __future__ import annotations
from dataclasses import dataclass, field
from voyager.env.bridge import VoyagerEnv
import threading
import asyncio
from termcolor import colored
from datetime import datetime


@dataclass
class Message:
    """
    Dataclass corresponding to a minecraft chat message.
    """
    username: str
    id: int
    content: str
    time: datetime = field(default_factory=datetime.now)  # Keep as a default if no time provided

    def __post_init__(self):
        # Check if `self.time` is an integer, assuming it's a timestamp in milliseconds
        if isinstance(self.time, int):
            # Convert milliseconds to seconds and then to a datetime object
            self.time = datetime.fromtimestamp(self.time / 1000)


class ChatService:
    """
    Service to handle chat messages in the Minecraft environment.
    """
    def __init__(self, env: VoyagerEnv, color: str = "red", partner: str = None):
        self.partner = partner
        self.unprocessed_messages : list[Message] = []
        self.processed_messages : list[Message] = []
        self.env = env
        self._stop_event = asyncio.Event()  # Stop signal for the asyncio loop
        self.poll_thread = None
        self.users = []
        self.color = color

    def get_all_messages_from_partner(self, partner) -> list[Message]:
        messages = []

        for message in self.unprocessed_messages:
            if message.username == partner:
                messages.append(message)

        for message in self.processed_messages:
            if message.username == partner:
                messages.append(message)

        return messages


    async def poll_messages_async(self):
        print("Starting async polling of messages.")
        while not self._stop_event.is_set():
            # Your message polling logic here
            await asyncio.sleep(1)
        print("Async polling of messages stopped.")

    def start_async_polling(self):
        if self.poll_thread and self.poll_thread.is_alive():
            print("Polling is already running.")
            return

        self._stop_event.clear()
        self.poll_thread = threading.Thread(target=self._run_event_loop, daemon=True)
        self.poll_thread.start()

    def _run_event_loop(self):
        """Run the async event loop that performs message polling."""
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        self.loop.run_until_complete(self.poll_messages_async())
        self.loop.close()

    def stop_async_polling(self):
        if self.poll_thread and self.poll_thread.is_alive():
            # Ensure the stop event is set within the thread's event loop
            self.loop.call_soon_threadsafe(self._stop_event.set)

            # Wait for the polling thread to finish
            self.poll_thread.join()
            print("Polling successfully stopped.")
        else:
            print("Polling is not running or has already been stopped.")


    def poll_chat(self):
        try:
            messages = self.env.poll_messages()

            # transform dictionnary of messages to Message objects
            messages = [Message(username=msg['username'], id=msg['id'], content=msg['content'], time=msg.get('time', 0)) for msg in messages]

            # Filter out already processed messages based on their IDs
            new_messages = [msg for msg in messages if msg.id not in {m.id for m in self.processed_messages} and msg.username in self.partner]
            print(new_messages)

            # sort messages by time
            new_messages.sort(key=lambda x: x.time)

            if new_messages:
                # Filter out messages containing "Gamerule"
                new_messages = [msg for msg in new_messages if "Gamerule" not in msg.content and "commands" not in msg.content and "Set the time" not in msg.content]
                self.unprocessed_messages.extend(new_messages)

                for message in new_messages:
                    if message.username not in self.users:
                        self.users.append(message.username)


            return new_messages
        except Exception as e:
            print(colored("Error while polling messages", self.color), e)


    def has_unprocessed_messages(self):
        return len(self.unprocessed_messages) > 0

    def get_all_messages(self):
        all_messages = self.processed_messages + self.unprocessed_messages
        all_messages.sort(key=lambda x: x.time)
        return all_messages

    def mark_chat_message_as_processed(self, message):
        if message in self.unprocessed_messages:
            self.unprocessed_messages.remove(message)
            self.processed_messages.append(message)
        else:
            raise ValueError(f"Message {message} not found in unprocessed messages")

    def send_message(self, content: str):
        """
        Sends a message in the Minecraft chat.

        Args:
            content (str): The content of the message to be sent.
        """
        try:
            self.env.send_chat_message(content)
            print(colored(f"Message sent: {content}", self.color))
        except Exception as e:
            print(colored(f"Error while sending message: {e}", self.color))
