from abc import ABC, abstractmethod

from sqlalchemy import select
from sqlalchemy.orm import Session

from synthetic_agents.common.functions import load_asset_file
from synthetic_agents.database.entity.prompt_template import PromptTemplate


class PromptTemplateLoader(ABC):
    """
    This class represents a generic loader that reads prompt templates from some source.
    """

    def __init__(self):
        """
        Creates a prompt template loader.

        :param postgres_connection_string: connection string to the Postgres database.
        """

        # This variable will store the template read from some source in a call to
        # initialize_loader. Then, a call to the database only needs to be performed once to load
        # the template into memory and passed to consumer classes on demand.
        self._template = None

    @property
    def template(self) -> str:
        """
        Gets a prompt template.

        :return: prompt template.
        """
        return self._template

    @abstractmethod
    def initialize_loader(self):
        """
        Reads the prompt template from some source and saves it into memory for later
        access by the consumer classes. Templates are not expect to change, so this read-once
        strategy should suffice and consume less resources.
        """
        pass


class TextPromptTemplateLoader(PromptTemplateLoader):
    """
    This class represents a loader that has a given text as a prompt template. It is mainly used
    for test purposes.
    """

    def __init__(self, template: str):
        """
        Creates a text prompt template loader.

        :param text: prompt template content.
        """
        super().__init__()

        self._template = template

    def initialize_loader(self):
        """
        Does nothing. The template is set with class instantiation.
        """
        pass


class PersistedAgentPromptTemplateLoader(PromptTemplateLoader):
    """
    This class represents a loader that reads agents' prompt templates from a database.
    """

    def __init__(
        self,
        application_type: str,
        agent_type: str,
        template_version: str,
        db: Session,
    ):
        """
        Creates a database prompt template loader.

        :param application_type: the type of the application the prompt template was designed for.
        :param agent_type: the type of the agent the prompt template was designed for.
        :param template_version: the version of the prompt template.
        :param db: database session.
        """
        super().__init__()

        self.application_type = application_type
        self.agent_type = agent_type
        self.template_version = template_version
        self.db = db

    def initialize_loader(self):
        """
        Reads the prompt template from the database and saves it into memory for later
        access by the consumer classes.

        :raise Exception: if no template was found with the parameters informed.
        """
        self._template = self.db.scalar(
            select(PromptTemplate.content).where(
                PromptTemplate.application_type == self.application_type,
                PromptTemplate.agent_type == self.agent_type,
                PromptTemplate.version == self.template_version,
            )
        )

        if self._template is None:
            raise Exception(
                f"No prompt template was found for application type = {self.application_type} "
                f", agent type = {self.application_type} and version = {self.template_version}"
            )


class LocalAssetPromptTemplateLoader(PromptTemplateLoader):
    """
    This class represents a loader that reads prompt templates from a local file saved as an asset
    in the project.
    """

    def __init__(self, asset_path: str):
        """
        Creates a local asset prompt template loader.

        :param asset_path: path of the file under the asset folder.
        """
        super().__init__()

        self.asset_path = asset_path

    def initialize_loader(self):
        """
        Reads the prompt template from the asset file and saves it into memory for later
        access by the consumer classes.

        :raise Exception: if no resource was found in the asset path.
        """

        self._template = load_asset_file(self.asset_path)
