"""
Metric utilities for processing and validating evaluation metrics.
"""

from typing import Dict, List, Optional, Sequence, TypedDict, Union

from galtea.utils.custom_score_metric import CustomScoreEvaluationMetric


class MetricInput(TypedDict, total=False):
    """
    Structured input format for specifying metrics when creating evaluations.

    This format provides flexibility by allowing you to identify metrics by ID or name,
    and optionally provide pre-calculated scores or custom scoring functions for self-hosted metrics.

    Attributes:
        id (Optional[str]): The unique identifier of the metric. Optional if name is provided.
            Must NOT be provided when score is a CustomScoreEvaluationMetric (the metric
            identifier is specified in the CustomScoreEvaluationMetric instance itself).
        name (Optional[str]): The name of the metric. Optional if id is provided.
            Must NOT be provided when score is a CustomScoreEvaluationMetric (the metric
            identifier is specified in the CustomScoreEvaluationMetric instance itself).
        score (Optional[Union[float, CustomScoreEvaluationMetric]]): Score for self-hosted metrics.
            - If float: Pre-calculated score between 0 and 1. Requires either 'id' or 'name'.
            - If CustomScoreEvaluationMetric: Custom scoring function that will be executed
              to calculate the score dynamically. The CustomScoreEvaluationMetric instance
              must contain either 'name' or 'id' to identify the metric. Do NOT provide
              'id' or 'name' in the MetricInput dict when using CustomScoreEvaluationMetric.
    """

    id: str
    name: str
    score: Union[float, CustomScoreEvaluationMetric]


def process_metrics_with_score_calculation(
    metrics: Sequence[Union[str, CustomScoreEvaluationMetric, MetricInput]],
    input_data: Optional[str],
    actual_output: Optional[str],
    expected_output: Optional[str],
    retrieval_context: Optional[str],
    context: Optional[str],
) -> List[Dict[str, Union[str, float]]]:
    """
    Processes metrics from various input formats and calculates scores for CustomScoreEvaluationMetric.

    This function ensures backward compatibility by accepting multiple input formats:
    - Strings (metric names) - deprecated format, maintained for legacy support
    - CustomScoreEvaluationMetric objects - deprecated format with dynamic score calculation
    - MetricInput dicts - current recommended format

    Args:
        metrics: List of metrics in any supported format.
        input_data: The input text or prompt.
        actual_output: The actual output generated by the system.
        expected_output: The expected output for comparison.
        retrieval_context: Context retrieved by a RAG system, if applicable.
        context: Additional context for the evaluation.

    Returns:
        List of metric dictionaries in standardized API format: [{ id?, name?, score? }, ...]
    """
    api_metrics: List[Dict[str, Union[str, float]]] = []

    for metric_obj in metrics:
        if isinstance(metric_obj, str):
            # Deprecated format: string metric name
            api_metrics.append({"name": metric_obj})
        elif isinstance(metric_obj, CustomScoreEvaluationMetric):
            # Deprecated format: CustomScoreEvaluationMetric - need to calculate score
            try:
                # Use __call__ to get validated score
                score = metric_obj(
                    input=input_data,
                    actual_output=actual_output,
                    expected_output=expected_output,
                    retrieval_context=retrieval_context,
                    context=context,
                )
                # CustomScoreEvaluationMetric can have either name or id
                metric_identifier: Dict[str, Union[str, float]] = {}
                if metric_obj.name is not None:
                    metric_identifier["name"] = metric_obj.name
                elif metric_obj.id is not None:
                    metric_identifier["id"] = metric_obj.id

                api_metrics.append({**metric_identifier, "score": score})
            except Exception as e:
                identifier = metric_obj.name or metric_obj.id or "unknown"
                raise ValueError(f"Failed to generate score for custom metric '{identifier}': {e}") from e
        elif isinstance(metric_obj, dict):
            # Current format: MetricInput dict
            metric_dict: Dict[str, Union[str, float]] = {}

            # Validate that when using CustomScoreEvaluationMetric as score,
            # id and name must NOT be provided (they're in the CustomScoreEvaluationMetric itself)
            if "score" in metric_obj and isinstance(metric_obj["score"], CustomScoreEvaluationMetric):
                if "id" in metric_obj or "name" in metric_obj:
                    raise ValueError(
                        "When using CustomScoreEvaluationMetric as score, do NOT provide 'id' or 'name' "
                        "in the MetricInput dict. The metric identifier should be specified in the "
                        "CustomScoreEvaluationMetric instance itself (via its 'name' or 'id' parameter)."
                    )

            if "id" in metric_obj:
                metric_dict["id"] = metric_obj["id"]
            if "name" in metric_obj:
                metric_dict["name"] = metric_obj["name"]

            if "score" in metric_obj:
                score_value = metric_obj["score"]
                if isinstance(score_value, CustomScoreEvaluationMetric):
                    # Need to calculate the score dynamically
                    try:
                        # Use __call__ to get validated score
                        calculated_score = score_value(
                            input=input_data,
                            actual_output=actual_output,
                            expected_output=expected_output,
                            retrieval_context=retrieval_context,
                            context=context,
                        )
                        # Add metric identifier from CustomScoreEvaluationMetric
                        if score_value.name is not None:
                            metric_dict["name"] = score_value.name
                        elif score_value.id is not None:
                            metric_dict["id"] = score_value.id

                        metric_dict["score"] = calculated_score
                    except Exception as e:
                        identifier = score_value.name or score_value.id or "unknown"
                        raise ValueError(f"Failed to generate score for custom metric '{identifier}': {e}") from e
                else:
                    # Pre-calculated score (float)
                    metric_dict["score"] = score_value

            api_metrics.append(metric_dict)
        else:
            raise TypeError(
                f"Invalid metric type: {type(metric_obj)}. Expected str, CustomScoreEvaluationMetric, or dict."
            )

    return api_metrics
