"""
---------------------------------------------------------------------------
Air Force Research Laboratory (AFRL) Autonomous Capabilities Team (ACT3)
Reinforcement Learning (RL) Core.

This is a US Government Work not subject to copyright protection in the US.

The use, dissemination or disclosure of data in this file is subject to
limitation or restriction. See accompanying README and LICENSE for details.
---------------------------------------------------------------------------
Classes that represent a metric generator

A metric generator is a class that generates a metric.
"""

from pydantic import BaseModel, ConfigDict, field_validator

from corl.evaluation.metrics.metric import Metric
from corl.evaluation.metrics.scopes import Scopes, from_string
from corl.evaluation.util.condition import NamedCondition


class ErrorAlert(Exception):
    """Custom exception to be raised when an alert with "error" is encountered"""


class Alert(BaseModel):
    """Representation of an active alert"""

    name: str
    type: str  # noqa: A003
    offense: Metric
    over_threshold: Metric
    model_config = ConfigDict(arbitrary_types_allowed=True)


class AlertGenerator(BaseModel):
    """Base class for a metric generator

    A metric generator is a class that is used to create a metric from parameters
    """

    name: str
    """Name of Alert
    """

    metric: str  # TODO.... that is not really a union
    """Metric to generate alert from
    """

    scope: str | Scopes
    """Scope to apply the alert to
    """

    thresholds: list[NamedCondition]
    """Thresholds to trigger specific alert types
    """

    @field_validator("scope")
    @classmethod
    def change_scope(cls, v):
        """Changes the given scope to a Scopes instance"""
        return from_string(v) if isinstance(v, str) else v

    def generate(self, metrics: dict[str, Metric]) -> list[Alert]:
        """Generate any alerts that are generated by the given thresholds

        May be multiple if there are multiple thresholds
        """
        if not isinstance(self.metric, str):
            raise RuntimeError(f'"metric" field must be string, received: {type(self.metric)}')

        # Generate the active alerts
        alerts = []
        for threshold in self.thresholds:
            if threshold.condition.func(metrics[self.metric]):
                # If an error then raise an exception
                if threshold.type == "error":
                    raise ErrorAlert(self.name)

                alerts.append(
                    Alert(
                        name=self.name,
                        type=threshold.type,
                        offense=metrics[self.metric],
                        over_threshold=metrics[self.metric] - threshold.condition.lhs,  # type: ignore[operator]
                    )
                )

        return alerts
