from typing import Literal, Tuple

import httpx
from loguru import logger

from elluminate.resources.base import BaseResource
from elluminate.schemas import (
    CreatePromptTemplateRequest,
    PromptTemplate,
    TemplateVariablesCollection,
)
from elluminate.utils import run_async


class PromptTemplatesResource(BaseResource):
    async def aget(
        self,
        name: str,
        version: int | Literal["latest"] = "latest",
    ) -> PromptTemplate:
        """Async version of get_prompt_template."""
        params = {}
        if name:
            params["name"] = name
        if version != "latest":
            params["version"] = str(version)

        response = await self._aget("prompt_templates", params=params)
        templates = [PromptTemplate.model_validate(template) for template in response.json().get("items", [])]
        if not templates:
            raise ValueError(f"No prompt template found with name {name} and version {version}")
        return templates[0]

    def get(
        self,
        name: str,
        version: int | Literal["latest"] = "latest",
    ) -> PromptTemplate:
        """Get a prompt template by name and version.

        Args:
            name (str): Name of the prompt template.
            version (int | Literal["latest"]): Version number or "latest". Defaults to "latest".

        Returns:
            (PromptTemplate): The requested prompt template.

        Raises:
            ValueError: If no template is found with given name and version.

        """
        return run_async(self.aget)(name, version)

    async def acreate(
        self,
        user_prompt_template: str,
        name: str,
        parent_prompt_template: PromptTemplate | None = None,
        default_collection: TemplateVariablesCollection | None = None,
    ) -> PromptTemplate:
        prompt_template_create = CreatePromptTemplateRequest(
            name=name,
            user_prompt_template_str=user_prompt_template,
            parent_prompt_template_id=parent_prompt_template.id if parent_prompt_template else None,
            default_collection_id=default_collection.id if default_collection else None,
        )
        response = await self._apost("prompt_templates", json=prompt_template_create.model_dump())
        return PromptTemplate.model_validate(response.json())

    def create(
        self,
        user_prompt_template: str,
        name: str,
        parent_prompt_template: PromptTemplate | None = None,
        default_collection: TemplateVariablesCollection | None = None,
    ) -> PromptTemplate:
        return run_async(self.acreate)(
            name=name,
            user_prompt_template=user_prompt_template,
            parent_prompt_template=parent_prompt_template,
            default_collection=default_collection,
        )

    async def aget_or_create(
        self,
        user_prompt_template: str,
        name: str,
        parent_prompt_template: PromptTemplate | None = None,
        default_collection: TemplateVariablesCollection | None = None,
    ) -> Tuple[PromptTemplate, bool]:
        """Async version of get_or_create_prompt_template."""
        try:
            return await self.acreate(
                user_prompt_template=user_prompt_template,
                name=name,
                parent_prompt_template=parent_prompt_template,
                default_collection=default_collection,
            ), True
        except httpx.HTTPStatusError as e:
            # Code 409 means resource already exists, simply get and return it
            if e.response.status_code == 409:
                ## If we got a conflict, extract the existing template ID and fetch it
                error_data = e.response.json()
                template_id = error_data.get("prompt_template_id")
                if template_id is None:
                    raise ValueError("Received 409 without prompt_template_id") from e

                response = await self._aget(f"prompt_templates/{template_id}")
                prompt_template = PromptTemplate.model_validate(response.json())

                differences = []
                if parent_prompt_template is not None and (
                    prompt_template.parent_prompt_template is None
                    or prompt_template.parent_prompt_template.id != parent_prompt_template.id
                ):
                    differences.append(
                        f"parent_prompt_template (expected: {parent_prompt_template.id}, actual: {prompt_template.parent_prompt_template.id if prompt_template.parent_prompt_template else None})"
                    )
                if default_collection is not None and (
                    prompt_template.default_template_variables_collection is None
                    or prompt_template.default_template_variables_collection.id != default_collection.id
                ):
                    differences.append(
                        f"default_collection (expected: {default_collection.id}, actual: {prompt_template.default_template_variables_collection.id if prompt_template.default_template_variables_collection else None})"
                    )

                if differences:
                    logger.warning(
                        f"Prompt template '{name}' already exists with different values for: {', '.join(differences)}. Returning existing template."
                    )
                return prompt_template, False
            raise  # Re-raise any other HTTP status errors

    def get_or_create(
        self,
        user_prompt_template: str,
        name: str,
        parent_prompt_template: PromptTemplate | None = None,
        default_collection: TemplateVariablesCollection | None = None,
    ) -> tuple[PromptTemplate, bool]:
        """Gets the prompt template by its name and user prompt contents if it exists.
        If the prompt template name does not exist, it creates a new prompt template with version 1.
        If a prompt template with the same name exists, but the user prompt is new,
        then it creates a new prompt template version with the new user prompt
        which will be the new latest version. When a prompt template with the same name and
        user prompt already exists, it returns the existing prompt template, ignoring the given
        parent_prompt_template, default_collection

        Args:
            user_prompt_template (str): The template string containing variables in {{variable}} format.
            name (str): Name for the template.
            parent_prompt_template (PromptTemplate | None): Optional parent template to inherit from.
            default_collection (TemplateVariablesCollection | None): Optional default template variables collection.

        Returns:
            tuple[PromptTemplate, bool]: A tuple containing:
                - The prompt template
                - Boolean indicating if a new template was created (True) or existing one returned (False)

        Raises:
            ValueError: If a 409 response is received without a prompt_template_id.

        """
        return run_async(self.aget_or_create)(
            user_prompt_template=user_prompt_template,
            name=name,
            parent_prompt_template=parent_prompt_template,
            default_collection=default_collection,
        )

    async def adelete(self, prompt_template: PromptTemplate) -> None:
        """Async version of delete."""
        await self._adelete(f"prompt_templates/{prompt_template.id}")

    def delete(self, prompt_template: PromptTemplate) -> None:
        """Deletes a prompt template.

        Args:
            prompt_template (PromptTemplate): The prompt template to delete.

        Raises:
            httpx.HTTPStatusError: If the prompt template doesn't exist or belongs to a different project.

        """
        return run_async(self.adelete)(prompt_template)
