from typing import List, Optional

from ...domain.models.metric_type import EvaluationParams, 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,
        evaluation_params: Optional[List[str]] = None,
        criteria: Optional[str] = None,
        evaluation_steps: Optional[List[str]] = None,
    ):
        """
        Create a new metric type.

        Args:
            name (str): Name of the metric type.
            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 are:
                - 'input': [Always required] The prompt or query 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.
            criteria (str, optional): Criteria for the metric type.
            evaluation_steps (list[str], optional): Evaluation steps for the metric type.


        Returns:
            Optional[MetricType]: The created metric type object, or None if an error occurs.
        """
        try:
            # Validate that all evaluation_params exist in EvaluationParams enum and convert to camel case
            camel_case_evaluation_params = []
            if evaluation_params:
                allowed_evaluation_params = [e.value for e in EvaluationParams]
                for param in evaluation_params:
                    lower_case_param = param.lower()
                    allowed_values = [e.value for e in EvaluationParams]
                    if lower_case_param not in allowed_evaluation_params:
                        raise ValueError(f"Invalid evaluation parameter: {param}. Must be one of {allowed_values}")
                    else:
                        camel_case_evaluation_params.append(
                            lower_case_param[0] + "".join(x for x in lower_case_param.title() if not x.isspace())[1:]
                        )

            metric_type = MetricTypeBase(
                name=name,
                criteria=criteria,
                evaluation_steps=evaluation_steps,
                evaluation_params=camel_case_evaluation_params,
            )

            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 ValueError(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
