import warnings
from typing import List, Optional, Union

from galtea.domain.exceptions.entity_not_found_exception import EntityNotFoundException
from galtea.domain.models.metric import Metric, MetricBase, MetricSource
from galtea.infrastructure.clients.http_client import Client
from galtea.utils.sort_order import SortOrder, normalize_sort_order
from galtea.utils.string import build_query_params, is_valid_id
from galtea.utils.test_type import TestType
from galtea.utils.timestamp import normalize_timestamp


class MetricService:
    """
    Service for managing Metrics.
    A Metric defines a way to evaluate and score product performance.
    """

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

    def create(
        self,
        name: str,
        test_type: Optional[Union[str, TestType]] = None,
        evaluator_model_name: Optional[str] = None,
        source: Optional[Union[str, MetricSource]] = None,
        judge_prompt: Optional[str] = None,
        evaluation_params: Optional[List[str]] = None,
        tags: Optional[List[str]] = None,
        description: Optional[str] = None,
        documentation_url: Optional[str] = None,
        user_group_ids: Optional[List[str]] = None,
    ):
        """
        Create a new metric.

        Args:
            name (str): Name of the metric.
            evaluator_model_name (str, optional): Name of the model to use for evaluation.
                Required for "LLM as a Judge" metrics.
                See https://docs.galtea.ai/sdk/api/metrics/create#param-evaluator-model-name for available models.
            source (str, optional): Source of the metric.
                Valid values are: `self_hosted`, `partial_prompt`, `full_prompt`, and `human_evaluation`.
            judge_prompt (str, optional): Custom prompt for the judge model. This defines an "LLM as a Judge"
                metric. Placeholders for product and test case information can be used.
                For `human_evaluation` metrics, this serves as the annotation criteria/rubric for human annotators.
            evaluation_params (list[str], optional): List of data that should be appended before the judge prompt.
                Required for `partial_prompt` and `human_evaluation` sources.
            tags (list[str], optional): Tags to help categorize and organize the metric.
            description (str, optional): Description of the metric.
            documentation_url (str, optional): Documentation URL for the metric.
            user_group_ids (list[str], optional): List of user group IDs to associate with this metric.
                For `human_evaluation` metrics, only users in these groups can annotate.
                The `evaluator_model_name` must not be provided when using `human_evaluation` source.

        Returns:
            Optional[Metric]: The created metric object, or None if an error occurs.
        """
        if test_type is not None:
            warnings.warn(
                (
                    "The 'test_type' parameter is deprecated and will be removed in a future release. "
                    "It is no longer used for metric creation."
                ),
                DeprecationWarning,
                stacklevel=2,
            )

        if not source:
            # warning to let the user know that the API will infer the source which may lead to undesired behavior
            warnings.warn(
                (
                    "'source' parameter was not provided. The API will infer the source which may lead to undesired behavior.\n"
                    "This implicit behavior is deprecated and will be removed in a future release.\n"
                    "Please explicitly specify the 'source' parameter."
                ),
                DeprecationWarning,
                stacklevel=2,
            )  # TODO: remove this warning in the next major release that has breaking changes along with the API code

        source = source.value if isinstance(source, MetricSource) else source

        try:
            metric = MetricBase(
                name=name,
                evaluator_model_name=evaluator_model_name,
                source=source,
                judge_prompt=judge_prompt,
                tags=tags,
                description=description,
                documentation_url=documentation_url,
                evaluation_params=evaluation_params,
                user_group_ids=user_group_ids,
            )

            metric.model_validate(metric.model_dump())
            response = self.__client.post("metrics", json=metric.model_dump(by_alias=True))
            metric_response = Metric(**response.json())
            return metric_response
        except Exception as e:
            print(f"Error creating Metric: {e}")
            return None

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

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

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

        response = self.__client.get(f"metrics/{metric_id}")
        return Metric(**response.json())

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

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

        Returns:
            Metric: The retrieved metric object.
        """
        query_params = build_query_params(names=[name])
        response = self.__client.get(f"metrics?{query_params}")
        metrics = [Metric(**metric) for metric in response.json()]

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

        return metrics[0]

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

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

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

        self.__client.delete(f"metrics/{metric_id}")

    def list(
        self,
        from_created_at: Optional[Union[str, int]] = None,
        to_created_at: Optional[Union[str, int]] = None,
        sort_by_created_at: Optional[Union[str, SortOrder]] = None,
        offset: Optional[int] = None,
        limit: Optional[int] = None,
        include_legacy: bool = False,
    ) -> List[Metric]:
        """
        Get a list of metrics.

        Args:
            from_created_at (str | int, optional): Filter metrics created at or after this timestamp.
                Accepts ISO 8601 string (e.g., '2024-01-01T00:00:00Z') or Unix timestamp (seconds).
            to_created_at (str | int, optional): Filter metrics created at or before this timestamp.
                Accepts ISO 8601 string (e.g., '2024-12-31T23:59:59Z') or Unix timestamp (seconds).
            sort_by_created_at (str | SortOrder, optional): Sort by created at. Valid values are 'asc' and 'desc'.
            offset (int, optional): Offset for pagination.
            limit (int, optional): Limit for pagination.
            include_legacy (bool, optional): Whether to include deprecated metrics in the results.
                Defaults to False. Set to True to also retrieve metrics that have been deprecated.

        Returns:
            List[Metric]: List of metrics.
        """
        sort_by_created_at = normalize_sort_order(sort_by_created_at) if sort_by_created_at is not None else None

        # Normalize date filter parameters
        from_created_at_normalized = normalize_timestamp(from_created_at)
        to_created_at_normalized = normalize_timestamp(to_created_at)

        query_params = build_query_params(
            fromCreatedAt=from_created_at_normalized,
            toCreatedAt=to_created_at_normalized,
            offset=offset,
            limit=limit,
            sort=["createdAt", sort_by_created_at] if sort_by_created_at else None,
            includeLegacy=include_legacy,
        )
        response = self.__client.get(f"metrics?{query_params}")
        metrics = [Metric(**metric) for metric in response.json()]
        return metrics
