from typing import List, Optional

from galtea.domain.exceptions.entity_not_found_exception import EntityNotFoundException

from ...domain.models.metric_type import MetricType, MetricTypeBase
from ...infrastructure.clients.http_client import Client
from ...utils.string import build_query_params, is_valid_id


class MetricTypeService:
    """
    Service for managing Metric Types.
    A Metric Type defines a way to evaluate and score product performance.
    """

    def __init__(self, client: Client):
        self._client = client

    def create(
        self,
        name: str,
        evaluator_model_name: Optional[str] = None,
        evaluation_params: Optional[List[str]] = None,
        criteria: Optional[str] = None,
        evaluation_steps: Optional[List[str]] = None,
        judge_prompt: Optional[str] = None,
        tags: Optional[List[str]] = None,
        description: Optional[str] = None,
        documentation_url: Optional[str] = None,
    ):
        """
        Create a new metric type.

        Args:
            name (str): Name of the metric type.
            evaluator_model_name (str, optional): Name of the model you want this metric to use for the evaluation.
                You can go to https://docs.galtea.ai/sdk/api/metrics/create#param-evaluator-model-name to see the list
                of available models.
            evaluation_params (list[str]): parameters that will be used for defining the criteria or
                evaluation_steps for the evaluation steps.

                For more information, refer to the documentation: https://docs.galtea.ai/concepts/metric-type

                Possible values for G-eval metrics are:
                - 'input': [Always required if criteria or evaluation_steps are defined] The prompt sent to the model.
                - 'actual_output': The actual output generated by the model.
                - 'expected_output': The ideal answer for the given input.
                - 'context': Additional background information provided to the model.
                - 'retrieval_context': The context retrieved by your RAG before sending the user query to your LLM.

                If creating a custom judge, the following parameters are also available:
                - 'product_description': The description of the product.
                - 'product_capabilities': The capabilities of the product.
                - 'product_inabilities': The inabilities of the product.
                - 'product_security_boundaries': The security boundaries of the product.

            criteria (str, optional): Criteria for the metric type.
            evaluation_steps (list[str], optional): Evaluation steps for the metric type.
            judge_prompt (str, optional): Custom Prompt for the judge model.
            tags (list[str], optional): Tags for the metric type to help categorize and organize them.
            description (str, optional): Description of the metric type.
            documentation_url (str, optional): Documentation URL for the metric type.

        Returns:
            Optional[MetricType]: The created metric type object, or None if an error occurs.
        """
        try:
            metric_type = MetricTypeBase(
                name=name,
                evaluator_model_name=evaluator_model_name,
                criteria=criteria,
                evaluation_steps=evaluation_steps,
                evaluation_params=evaluation_params,
                judge_prompt=judge_prompt,
                tags=tags,
                description=description,
                documentation_url=documentation_url,
            )

            metric_type.model_validate(metric_type.model_dump())
            response = self._client.post("metricTypes", json=metric_type.model_dump(by_alias=True))
            metric_type_response = MetricType(**response.json())
            return metric_type_response
        except Exception as e:
            print(f"Error creating Metric Type: {e}")
            return None

    def get(self, metric_type_id: str):
        """
        Retrieve a metric type by its ID.

        Args:
            metric_type_id (str): ID of the metric type to retrieve.

        Returns:
            MetricType: The retrieved metric type object.
        """
        if not is_valid_id(metric_type_id):
            raise ValueError("Metric type ID provided is not valid.")

        response = self._client.get(f"metricTypes/{metric_type_id}")
        return MetricType(**response.json())

    def get_by_name(self, name: str):
        """
        Retrieve a metric type by its name.

        Args:
            name (str): Name of the metric type to retrieve.

        Returns:
            MetricType: The retrieved metric type object.
        """
        query_params = build_query_params(names=[name])
        response = self._client.get(f"metricTypes?{query_params}")
        metricTypes = [MetricType(**metric_type) for metric_type in response.json()]

        if not metricTypes:
            raise EntityNotFoundException(f"Metric type with name {name} does not exist.")

        return metricTypes[0]

    def delete(self, metric_type_id: str):
        """
        Delete a metric type by its ID.

        Args:
            metric_type_id (str): ID of the metric type to delete.

        Returns:
            None: None.
        """
        if not is_valid_id(metric_type_id):
            raise ValueError("Metric type ID provided is not valid.")

        self._client.delete(f"metricTypes/{metric_type_id}")

    def list(self, offset: Optional[int] = None, limit: Optional[int] = None) -> List[MetricType]:
        """
        Get a list of metric types.

        Args:
            offset (int, optional): Offset for pagination.
            limit (int, optional): Limit for pagination.

        Returns:
            List[MetricType]: List of metric types.
        """
        query_params = build_query_params(offset=offset, limit=limit)
        response = self._client.get(f"metricTypes?{query_params}")
        metric_types = [MetricType(**metric_type) for metric_type in response.json()]
        return metric_types
