from dapr_agents.types.llm import PromptyModelConfig, OpenAIModelConfig, AzureOpenAIModelConfig, PromptyDefinition
from dapr_agents.prompt.base import PromptTemplateBase
from dapr_agents.prompt.chat import ChatPromptTemplate
from dapr_agents.prompt.string import StringPromptTemplate
from dapr_agents.prompt.utils.prompty import PromptyHelper
from typing import Dict, Any, Union, Optional, Literal, List, Tuple
from pathlib import Path
import logging

logger = logging.getLogger(__name__)

class Prompty(PromptyDefinition):
    """
    A class to handle loading and formatting of Prompty templates for language models workflows.
    """

    def extract_input_variables(self, template_format: Literal["f-string", "jinja2"] = "jinja2") -> Tuple[List[str], List[str]]:
        """
        Extract all input variables from the Prompty instance, including placeholders from content, 
        predefined inputs, and sample inputs. This method returns both regular input variables and 
        more complex placeholders that may require additional processing.

        Args:
            template_format (Literal["f-string", "jinja2"]): Template format for content parsing. Default is 'jinja2'.

        Returns:
            Tuple[List[str], List[str]]: 
                - A list of regular input variables.
                - A list of placeholders that may require extra processing (e.g., loops or attributes).
        """
        # Extract undeclared variables and placeholders from the content
        undeclared_variables = PromptyHelper.extract_placeholders_from_content(self.content, template_format)

        # Gather predefined inputs and sample inputs, default to empty dict if they are None
        predefined_inputs = list(self.inputs.keys()) if self.inputs else []
        sample_inputs = list(self.sample.keys()) if self.sample else []

        # Combine undeclared variables (regular inputs), filtered predefined inputs, and sample inputs
        regular_variables = list(set(undeclared_variables + predefined_inputs + sample_inputs))

        return regular_variables

    def to_prompt_template(self, template_format: Literal["f-string", "jinja2"] = "jinja2") -> PromptTemplateBase:
        """
        Convert this Prompty instance into a PromptTemplateBase instance by pre-processing the content
        into a list of messages (for chat) or a string (for completion).
        No inputs are provided at this stage, so placeholders and dynamic parts remain in the template.

        Args:
            template_format (Literal["f-string", "jinja2"]): Template format for content parsing. Default is 'jinja2'.

        Returns:
            PromptTemplateBase: An instance of ChatPromptTemplate or StringPromptTemplate.
        """
        # Ensure that content is present in the Prompty instance
        if not self.content:
            raise ValueError("Prompty instance is missing 'content'. Cannot convert to prompt template.")

        # Extract input variables and placeholders using the updated method
        regular_variables = self.extract_input_variables(template_format)

        # Pre-process the content into a list of messages for chat-based prompts
        if self.model.api == 'chat':
            # Process the content into messages using PromptyHelper
            messages = PromptyHelper.to_prompt(self.content, api_type='chat')
            return ChatPromptTemplate(
                input_variables=regular_variables,
                messages=messages,
                template_format=template_format,
            )
        else:
            return StringPromptTemplate(
                input_variables=regular_variables,
                template=self.content,
                template_format=template_format,
            )

    @classmethod
    def load(cls, prompty_source: Union[str, Path], model: Optional[Union[OpenAIModelConfig, AzureOpenAIModelConfig, Dict[str, Any]]] = None) -> 'Prompty':
        """
        Load a Prompty template from a file or inline content and configure the Prompty object.

        Args:
            prompty_source (Union[str, Path]): Path to the Prompty file or inline content as a string.
            model (Optional[Union[OpenAIModelConfig, AzureOpenAIModelConfig, Dict[str, Any]]]): Optional model configuration to override.

        Returns:
            Prompty: A validated Prompty object.
        """
        # Convert prompty_source to Path if it seems like a file path
        if isinstance(prompty_source, str) and prompty_source.endswith(".prompty"):
            path_object = Path(prompty_source).resolve()
        elif isinstance(prompty_source, Path):
            path_object = prompty_source.resolve()
        else:
            path_object = None

        # Determine if we're dealing with a file path or inline content
        if path_object and path_object.exists():
            # Use file path, resolving any path-based references
            metadata, content = PromptyHelper.parse_prompty_content(path_object)
            metadata = PromptyHelper.normalize(metadata, parent=path_object.parent)
        else:
            # Treat prompty_source as inline content
            metadata, content = PromptyHelper.parse_prompty_content(prompty_source)
            metadata = PromptyHelper.normalize(metadata, parent=Path().resolve())
        
        # Override the model if provided
        if model:
            if isinstance(model, dict):
                metadata['model'] = PromptyModelConfig(**model)
            else:
                metadata['model'] = model.model_dump()

        # Validate and construct the Prompty object
        metadata['content'] = content
        return cls.model_validate(metadata)