import uuid
from typing import TYPE_CHECKING, Dict, List, Optional

if TYPE_CHECKING:
    from literalai.event_processor import EventProcessor

from literalai.context import active_steps_var, active_thread_var, active_root_run_var
from literalai.helper import utc_now
from literalai.my_types import Utils
from literalai.observability.step import MessageStepType, StepDict, Score, Attachment


class Message(Utils):
    id: Optional[str] = None
    name: Optional[str] = None
    type: Optional[MessageStepType] = None
    metadata: Optional[Dict] = {}
    parent_id: Optional[str] = None
    timestamp: Optional[str] = None
    content: str
    thread_id: Optional[str] = None
    root_run_id: Optional[str] = None
    tags: Optional[List[str]] = None
    created_at: Optional[str] = None

    scores: List[Score] = []
    attachments: List[Attachment] = []

    def __init__(
        self,
        content: str,
        id: Optional[str] = None,
        type: Optional[MessageStepType] = "assistant_message",
        name: Optional[str] = None,
        thread_id: Optional[str] = None,
        parent_id: Optional[str] = None,
        scores: List[Score] = [],
        attachments: List[Attachment] = [],
        metadata: Optional[Dict] = {},
        timestamp: Optional[str] = None,
        tags: Optional[List[str]] = [],
        processor: Optional["EventProcessor"] = None,
        root_run_id: Optional[str] = None,
    ):
        from time import sleep

        sleep(0.001)

        self.id = id or str(uuid.uuid4())
        if not timestamp:
            self.timestamp = utc_now()
        else:
            self.timestamp = timestamp
        self.name = name
        self.type = type
        self.content = content
        self.scores = scores
        self.attachments = attachments
        self.metadata = metadata
        self.tags = tags

        self.processor = processor

        # priority for thread_id: thread_id > parent_step.thread_id > active_thread
        self.thread_id = thread_id

        # priority for root_run_id: root_run_id > parent_step.root_run_id > active_root_run
        self.root_run_id = root_run_id

        # priority for parent_id: parent_id > parent_step.id
        self.parent_id = parent_id

    def end(self):
        active_steps = active_steps_var.get()

        if len(active_steps) > 0:
            parent_step = active_steps[-1]
            if not self.parent_id:
                self.parent_id = parent_step.id
            if not self.thread_id:
                self.thread_id = parent_step.thread_id
            if not self.root_run_id:
                self.root_run_id = parent_step.root_run_id

        if not self.thread_id:
            if active_thread := active_thread_var.get():
                self.thread_id = active_thread.id

        if not self.root_run_id:
            if active_root_run := active_root_run_var.get():
                self.root_run_id = active_root_run.id

        if not self.thread_id and not self.parent_id:
            raise Exception(
                "Message must be initialized with a thread_id or a parent id."
            )

        if self.processor is None:
            raise Exception(
                "Message must be initialized with a processor to allow finalization."
            )
        self.processor.add_event(self.to_dict())

    def to_dict(self) -> "StepDict":
        # Create a correct step Dict from a message
        return {
            "id": self.id,
            "metadata": self.metadata,
            "parentId": self.parent_id,
            "startTime": self.timestamp,
            "endTime": self.timestamp,  # startTime = endTime in Message
            "type": self.type,
            "threadId": self.thread_id,
            "output": {
                "content": self.content
            },  # no input, output = content in Message
            "name": self.name,
            "tags": self.tags,
            "scores": [score.to_dict() for score in self.scores],
            "attachments": [attachment.to_dict() for attachment in self.attachments],
            "rootRunId": self.root_run_id,
        }

    @classmethod
    def from_dict(cls, message_dict: Dict) -> "Message":
        id = message_dict.get("id", None)
        type = message_dict.get("type", None)
        thread_id = message_dict.get("threadId", None)
        root_run_id = message_dict.get("rootRunId", None)

        metadata = message_dict.get("metadata", None)
        parent_id = message_dict.get("parentId", None)
        timestamp = message_dict.get("startTime", None)
        content = message_dict.get("output", {}).get("content", "")
        name = message_dict.get("name", None)
        scores = message_dict.get("scores", [])
        attachments = message_dict.get("attachments", [])

        message = cls(
            id=id,
            metadata=metadata,
            parent_id=parent_id,
            timestamp=timestamp,
            type=type,
            thread_id=thread_id,
            content=content,
            name=name,
            scores=scores,
            attachments=attachments,
            root_run_id=root_run_id,
        )

        message.created_at = message_dict.get("createdAt", None)

        return message
