"""
프롬프트 CRUD 및 Inference Client

API를 통한 프롬프트 관리 기능을 제공하는 클라이언트입니다.
CRUD 기능과 Inference 기능을 모두 제공합니다.
"""

import ast
import requests
from typing import Dict, Any, Optional, List, Union, Literal, Tuple
from requests.exceptions import RequestException
from adxp_sdk.auth import BaseCredentials
from adxp_sdk.base import BaseClient
from langchain_core.prompts import ChatPromptTemplate
from adxp_sdk.prompts.utils import replace_braces_for_prompt

PromptVersionType = Union[Literal["latest"], Literal["release"], str]

from .schemas import (
    PromptCreateRequest,
    PromptUpdateRequest,
    FewShotsCreateRequest,
    FewShotsUpdateRequest,
)


class PromptClient(BaseClient):
    """프롬프트 CRUD 및 Inference API 클라이언트"""

    def __init__(
        self,
        credentials: BaseCredentials
     ):
        """
        PromptClient 초기화

        Args:
            credentials: 인증 정보 (BaseCredentials)
        """
        super().__init__(credentials)

    
    def _parse_prompt_string(self, prompt_str: str) -> List[Tuple[str, str]]:
        """
        Parse prompt string to list of tuples.
        
        Args:
            prompt_str: Prompt string to parse
            
        Returns:
            List of (role, content) tuples
        """
        try:
            # convert to list of tuples
            parsed = ast.literal_eval(prompt_str)
            if not isinstance(parsed, list):
                raise ValueError("Parsed result is not a list.")
            return parsed
        except (SyntaxError, ValueError) as e:
            raise ValueError(f"Failed to parse prompt string: {str(e)}")


    # ====================================================================
    # 핵심 CRUD Operations
    # ====================================================================

    def create_prompt(
        self,
        prompt_data: Union[PromptCreateRequest, Dict[str, Any]]
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        프롬프트 생성

        Args:
            prompt_data: 프롬프트 생성 데이터 (template 필드가 있으면 템플릿 기반 생성)

        Returns:
            생성된 프롬프트 정보
        """
        if isinstance(prompt_data, PromptCreateRequest):
            data = prompt_data.model_dump(exclude_none=True)
        else:
            data = prompt_data.copy()
        
        # tags가 없으면 빈 리스트를 기본값으로 제공
        if 'tags' not in data or data.get('tags') is None:
            data['tags'] = []

        # template 필드가 있으면 템플릿 기반으로 처리
        if 'template' in data and data['template']:
            try:
                template_name = data.pop('template')  # template 필드 제거

                # 템플릿 데이터 가져오기
                templates_response = self.get_templates()
                if not isinstance(templates_response, dict):
                    raise Exception(f"Unexpected templates_response type: expected dict, got {type(templates_response)}")
                if not templates_response.get('data'):
                    raise Exception("템플릿 목록을 가져올 수 없습니다.")
                templates_data = templates_response['data']
                if not isinstance(templates_data, list):
                    raise Exception(f"Unexpected templates_data type: expected list, got {type(templates_data)}")

                # 선택된 템플릿 찾기
                template = None
                for t in templates_data:
                    if not isinstance(t, dict):
                        continue
                    if t.get('name') == template_name:
                        template = t
                        break

                if not template:
                    available_templates = [
                        t.get('name') for t in templates_data if isinstance(t, dict)
                    ]
                    raise Exception(f"템플릿 '{template_name}'을 찾을 수 없습니다. 사용 가능한 템플릿: {available_templates}")

                # 템플릿 데이터로 메시지와 변수 채우기
                template_variables = template.get('variables', [])
                formatted_variables = []
                for var in template_variables:
                    formatted_var = var.copy()
                    # variable 필드가 {{}} 없이 있다면 추가
                    if 'variable' in formatted_var and not formatted_var['variable'].startswith('{{'):
                        formatted_var['variable'] = f"{{{{{formatted_var['variable']}}}}}"
                    formatted_variables.append(formatted_var)

                # 템플릿 데이터로 오버라이드
                data.update({
                    "name": data.get('name', template.get('name', '')),
                    "desc": data.get('desc', f"Template: {template.get('name', '')}"),
                    "messages": template.get('messages', []),
                    "tags": data.get('tags', template.get('tags', [])),
                    "variables": formatted_variables,
                    "release": data.get('release', False)
                })
            except Exception as e:
                # 템플릿 처리 실패 시 원래 데이터로 진행
                print(f"Template processing failed, proceeding with original data: {e}")
                pass

        return self._make_request(
            "POST",
            "/api/v1/agent/inference-prompts",
            json_data=data
        )

    def get_prompts(
        self,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None,
        fetch_mode: Optional[str] = "lazy"
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        프롬프트 목록 조회

        Args:
            page: 페이지 번호 (기본값: 1)
            size: 페이지 크기 (기본값: 10)
            sort: 정렬 기준
            filter: 필터 조건
            search: 검색어

        Returns:
            프롬프트 목록
        """
        params: Dict[str, Any] = {
            "page": page,
            "size": size
        }

        if sort:
            params["sort"] = sort
        if filter:
            params["filter"] = filter
        if search:
            params["search"] = search

        params["fetch_mode"] = fetch_mode
        
        return self._make_request(
            method="GET",
            endpoint="/api/v1/agent/inference-prompts",
            params=params
        )

    def get_prompt(self, prompt_uuid: str) -> Union[Dict[str, Any], List[Any], Any]:
        """
        특정 프롬프트 조회

        Args:
            prompt_uuid: 프롬프트 UUID

        Returns:
            프롬프트 정보
        """
        return self._make_request(
            method="GET",
            endpoint=f"/api/v1/agent/inference-prompts/{prompt_uuid}"
        )

    def update_prompt(
        self,
        prompt_uuid: str,
        prompt_data: Union[PromptUpdateRequest, Dict[str, Any]]
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        프롬프트 수정

        Args:
            prompt_uuid: 프롬프트 UUID
            prompt_data: 수정할 프롬프트 데이터

        Returns:
            수정된 프롬프트 정보
        """
        if isinstance(prompt_data, PromptUpdateRequest):
            data = prompt_data.model_dump(exclude_none=True)
        else:
            data = prompt_data.copy()
        
        # 필수 필드에 기본값 제공 (messages, variables, tags는 필수)
        if 'messages' not in data or data.get('messages') is None:
            data['messages'] = []
        if 'variables' not in data or data.get('variables') is None:
            data['variables'] = []
        if 'tags' not in data or data.get('tags') is None:
            data['tags'] = []

        return self._make_request(
            method="PUT",
            endpoint=f"/api/v1/agent/inference-prompts/{prompt_uuid}",
            json_data=data
        )

    def delete_prompt(self, prompt_uuid: str) -> Union[Dict[str, Any], List[Any], Any]:
        """
        프롬프트 삭제

        Args:
            prompt_uuid: 프롬프트 UUID

        Returns:
            삭제 결과
        """
        return self._make_request(
            method="DELETE",
            endpoint=f"/api/v1/agent/inference-prompts/{prompt_uuid}"
        )

    # ====================================================================
    # 템플릿 관련 기능
    # ====================================================================

    def get_templates(self) -> Union[Dict[str, Any], List[Any], Any]:
        """
        내장 템플릿 목록 조회

        Returns:
            템플릿 목록
        """
        return self._make_request(
            method="GET",
            endpoint="/api/v1/agent/inference-prompts/templates/builtin"
        )

    def get_prompt_messages_and_variables(
        self,
        prompt_uuid: str
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Get a prompt messages and variables.

        Args:
            prompt_uuid (str): Prompt UUID

        Returns:
            Dict[str, Any]: Prompt data containing messages and variables

        Raises:
            requests.RequestException: If the request fails
        """
        return self._make_request(
            method="GET",
            endpoint=f"/api/v1/agent/inference-prompts/prompt/{prompt_uuid}"
        )

    # ====================================================================
    # Guardrails 관련 기능
    # ====================================================================

    def get_guardrails(
        self,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Get Guardrails 목록 조회

        Args:
            page: 페이지 번호 (기본값: 1)
            size: 페이지 크기 (기본값: 10)
            sort: 정렬 기준 (기본값: None)
            filter: 필터 조건 (기본값: None)
            search: 검색어 (기본값: None)

        Returns:
            Dict[str, Any]: Guardrails 목록

        Raises:
            requests.RequestException: If the request fails
        """
        params: Dict[str, Any] = {
            "page": page,
            "size": size
        }

        if sort:
            params["sort"] = sort
        if filter:
            params["filter"] = filter
        if search:
            params["search"] = search

        return self._make_request(
            method="GET",
            endpoint="/api/v1/agent/guardrails",
            params=params
        )

    def create_guardrails(
        self,
        name: str,
        description: str,
        prompt_id: str,
        llms: List[Dict[str, str]],
        tags: List[Dict[str, str]]
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Create Guardrails

        Args:
            name: Guardrails 이름
            description: Guardrails 설명
            prompt_id: 프롬프트 ID
            llms: LLM 서빙 정보 리스트 [{"serving_name": "name"}]
            tags: 태그 리스트 [{"tag": "tag_name"}]

        Returns:
            Dict[str, Any]: 생성된 Guardrails 정보 (Guardrails ID 포함)

        Raises:
            requests.RequestException: If the request fails
        """
        guardrails_data = {
            "name": name,
            "description": description,
            "prompt_id": prompt_id,
            "llms": llms,
            "tags": tags
        }

        return self._make_request(
            method="POST",
            endpoint="/api/v1/agent/guardrails",
            json_data=guardrails_data
        )

    def get_guardrails_by_id(self, guardrails_id: str) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Get a single item of Guardrails

        Args:
            guardrails_id: Guardrails ID

        Returns:
            Dict[str, Any]: Guardrails 상세 정보

        Raises:
            requests.RequestException: If the request fails
        """
        return self._make_request(
            method="GET",
            endpoint=f"/api/v1/agent/guardrails/{guardrails_id}"
        )

    def update_guardrails(
        self,
        guardrails_id: str,
        name: str,
        description: str,
        prompt_id: str,
        llms: List[Dict[str, str]],
        tags: List[Dict[str, str]]
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Edit Guardrails

        Args:
            guardrails_id: Guardrails ID
            name: Guardrails 이름
            description: Guardrails 설명
            prompt_id: 프롬프트 ID
            llms: LLM 서빙 정보 리스트 [{"serving_name": "name"}]
            tags: 태그 리스트 [{"tag": "tag_name"}]

        Returns:
            Dict[str, Any]: 수정된 Guardrails 정보

        Raises:
            requests.RequestException: If the request fails
        """
        guardrails_data = {
            "name": name,
            "description": description,
            "prompt_id": prompt_id,
            "llms": llms,
            "tags": tags
        }

        return self._make_request(
            method="PUT",
            endpoint=f"/api/v1/agent/guardrails/{guardrails_id}",
            json_data=guardrails_data
        )

    def delete_guardrails(self, guardrails_id: str) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Delete Guardrails

        Args:
            guardrails_id: Guardrails ID

        Returns:
            Dict[str, Any]: 삭제 결과

        Raises:
            requests.RequestException: If the request fails
        """
        return self._make_request(
            method="DELETE",
            endpoint=f"/api/v1/agent/guardrails/{guardrails_id}"
        )

    def get_guardrails_by_serving(self, serving_name: str) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Get a Guardrails Prompt by Serving Name

        Args:
            serving_name: Serving Name

        Returns:
            Dict[str, Any]: Guardrails 정보

        Raises:
            requests.RequestException: If the request fails
        """
        serving_data = {
            "serving_name": serving_name
        }

        return self._make_request(
            method="POST",
            endpoint="/api/v1/agent/guardrails/serving",
            json_data=serving_data
        )

    def get_guardrails_tags(self) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Get Guardrails Tags

        Returns:
            Dict[str, Any]: 태그 목록 (list[str])

        Raises:
            requests.RequestException: If the request fails
        """
        return self._make_request(method="GET", endpoint="/api/v1/agent/guardrails/list/tags")

    def search_guardrails_tags(self, filters: str) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Search Guardrails Tags

        Args:
            filters: 검색 필터

        Returns:
            Dict[str, Any]: GuardrailsListResponse

        Raises:
            requests.RequestException: If the request fails
        """
        params = {"filters": filters}
        return self._make_request(
            method="GET",
            endpoint="/api/v1/agent/guardrails/search/tags",
            params=params
        )

    # ====================================================================
    # Few-shots 관련 기능
    # ====================================================================

    def create_few_shot(
        self,
        few_shot_data: Union[FewShotsCreateRequest, Dict[str, Any]]
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Few Shots 생성

        Args:
            few_shot_data: Few Shots 생성 데이터
        Returns:
            생성된 Few Shots 정보
        """
        if isinstance(few_shot_data, FewShotsCreateRequest):
            data = few_shot_data.model_dump(exclude_none=True)
        else:
            data = few_shot_data.copy()
        return self._make_request(
            method="POST",
            endpoint="/api/v1/agent/few-shots",
            json_data=data
        )

    def get_few_shots(
        self,
        page: int = 1,
        size: int = 10,
        sort: Optional[str] = None,
        filter: Optional[str] = None,
        search: Optional[str] = None
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Few Shots 목록 조회

        Args:
            page: 페이지 번호 (기본값: 1)
            size: 페이지 크기 (기본값: 10)
            sort: 정렬 기준
            filter: 필터 조건
            search: 검색어

        Returns:
            Few Shots 목록
        """
        params: Dict[str, Any] = {
            "page": page,
            "size": size
        }
        if sort:
            params["sort"] = sort
        if filter:
            params["filter"] = filter
        if search:
            params["search"] = search

        return self._make_request(
            method="GET",
            endpoint="/api/v1/agent/few-shots",
            params=params
        )

    def get_few_shot(
        self,
        few_shot_uuid: str
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        특정 Few Shot 조회

        Args:
            few_shot_uuid: Few Shot UUID

        Returns:
            Few Shot 정보
        """
        return self._make_request(
            method="GET",
            endpoint=f"/api/v1/agent/few-shots/{few_shot_uuid}"
        )

    def get_few_shot_items(
        self,
        few_shot_uuid: str
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        특정 릴리즈 Few Shot items 조회

        Args:
            few_shot_uuid: Few Shot UUID

        Returns:
            Few Shot 정보
        """
        return self._make_request(
            method="GET",
            endpoint=f"/api/v1/agent/few-shots/api/{few_shot_uuid}"
        )

    def update_few_shot(
        self,
        few_shot_uuid: str,
        few_shot_data: Union[FewShotsUpdateRequest, Dict[str, Any]]
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Few Shots 수정

        Args:
            few_shot_uuid: Few Shot UUID
            few_shot_data: 수정할 Few Shot 데이터

        Returns:
            수정된 Few Shot 정보
        """
        if isinstance(few_shot_data, FewShotsUpdateRequest):
            data = few_shot_data.model_dump(exclude_none=True)
        else:
            data = few_shot_data.copy()

        return self._make_request(
            method="PUT",
            endpoint=f"/api/v1/agent/few-shots/{few_shot_uuid}",
            json_data=data
        )

    def delete_few_shot(
        self,
        few_shot_uuid: str
    ) -> Union[Dict[str, Any], List[Any], Any]:
        """
        Few Shots 삭제

        Args:
            few_shot_uuid: Few Shot UUID

        Returns:
            삭제 결과
        """
        return self._make_request(
            method="DELETE",
            endpoint=f"/api/v1/agent/few-shots/{few_shot_uuid}"
        )
    
    # ====================================================================
    # Inference Operations (from PromptHub)
    # ====================================================================
    
    def get_release_prompt(self, prompt_id: str) -> List[Tuple[str, str]]:
        """
        Get release prompt as list of tuples.
        
        Args:
            prompt_id: Prompt ID
            
        Returns:
            List of (role, content) tuples
        """
        try:
            response_data = self._make_request(
                method="GET",
                endpoint=f"/api/v1/agent/inference-prompts/test/{prompt_id}"
            )

            if not isinstance(response_data, dict):
                raise RuntimeError(f"Unexpected response_data type: expected dict, got {type(response_data)}")
            if "data" in response_data:
                prompt_str = response_data["data"]
                return self._parse_prompt_string(prompt_str)
            else:
                raise RuntimeError(f"Cannot find prompt: {prompt_id} (version: release)")

        except RequestException as e:
            raise RuntimeError(f"Failed to get prompt: {str(e)}")

    def get_prompt_template(
        self, prompt_id: str, version: PromptVersionType = "release"
    ) -> ChatPromptTemplate:
        """
        Get a prompt template from the prompt client.

        Args:
            prompt_id (str): Prompt ID
            version (PromptVersionType): version ("latest", "release")

        Returns:
            ChatPromptTemplate: LangChain ChatPromptTemplate object

        Example:
            >>> client = PromptClient(credentials)
            >>> prompt_template = client.get_prompt_template("prompt_id", "release")
        """
        if version == "release":
            template_contents = self.get_release_prompt(prompt_id)
            template_contents = [
                (role, replace_braces_for_prompt(content))
                for role, content in template_contents
            ]
            prompt_template = ChatPromptTemplate(template_contents)
            return prompt_template
        elif version == "latest":
            raise RuntimeError("latest version is not supported")
        else:
            raise ValueError(f"Invalid version: {version}")
