"""
Requirement Constraint (RC) Evaluators are evaluators that check if data are required under given circumstances.
Typical usecases are for example
* you must only provide a Gerätenummer if the Transaktionsgrund is f.e. 'E08'
* you must only provide an Ausbaudatum if the meter is being removed f.e. 'Z02'
"""
import asyncio
from abc import ABC, abstractmethod
from typing import Dict, List, Optional

from ahbicht.content_evaluation.evaluationdatatypes import EvaluatableData, EvaluationContext
from ahbicht.content_evaluation.evaluators import Evaluator
from ahbicht.expressions.condition_nodes import ConditionFulfilledValue


# pylint: disable=no-self-use
class RcEvaluator(Evaluator, ABC):
    """
    Base class of all Requirement Constraint (RC) evaluators.
    To implement a content_evaluation create or update an inheriting class that has edifact_format and
    edifact_format_version set accordingly. Then create a method named "evaluate_123" where "123" is the condition key
    of the condition it evaluates.
    """

    def __init__(self, evaluatable_data: EvaluatableData):
        """
        Instantiate any evaluator by providing some evaluatable data from a message.
        :param evaluatable_data:
        """
        if not evaluatable_data:
            raise ValueError("Evaluatable data have to be provided to any evaluator.")
        self.evaluatable_data = evaluatable_data

    @abstractmethod
    def _get_default_context(self) -> EvaluationContext:
        raise NotImplementedError("Has to be implemented in inheriting class")

    async def evaluate_single_condition(
        self, condition_key: str, context: Optional[EvaluationContext] = None
    ) -> ConditionFulfilledValue:
        """
        Evaluates the condition with the given key.
        :param context: optional content_evaluation context, if not the default context will be used
        :param condition_key: key of the condition, e.g. "78"
        :return:
        """
        evaluation_method = self.get_evaluation_method(condition_key)
        if evaluation_method is None:
            raise NotImplementedError(f"There is no content_evaluation method for condition '{condition_key}'")
        if context is None:
            context = self._get_default_context()
        result = await evaluation_method(context)
        return result

    async def evaluate_conditions(
        self, condition_keys: List[str], condition_keys_with_context: Optional[Dict[str, EvaluationContext]] = None
    ) -> Dict[str, ConditionFulfilledValue]:
        """
        Validate all the conditions provided in condition_keys in their respective context.
        """
        if condition_keys_with_context is None:
            tasks = [self.evaluate_single_condition(condition_key, None) for condition_key in condition_keys]
        else:
            tasks = []
            for condition_key in condition_keys:
                if condition_key not in condition_keys_with_context:
                    tasks.append(self.evaluate_single_condition(condition_key, None))
                else:
                    tasks.append(
                        self.evaluate_single_condition(condition_key, condition_keys_with_context[condition_key])
                    )

        results = await asyncio.gather(*tasks)

        result = dict(zip(condition_keys, results))
        return result


class DictBasedRcEvaluator(RcEvaluator):
    """
    A requirement constraint evaluator that is initialized with a prefilled dictionary.
    """

    def __init__(self, results: Dict[str, ConditionFulfilledValue]):
        """
        Initialize with a dictionary that contains all the requirement constraint evaluation results.
        :param results:
        """
        super().__init__(evaluatable_data=EvaluatableData(edifact_seed=results))
        self._results: Dict[str, ConditionFulfilledValue] = results

    def _get_default_context(self) -> EvaluationContext:
        raise NotImplementedError()

    # pylint:disable=unused-argument
    async def evaluate_single_condition(
        self, condition_key: str, context: Optional[EvaluationContext] = None
    ) -> ConditionFulfilledValue:
        try:
            return self._results[condition_key]
        except KeyError as key_error:
            raise NotImplementedError(f"No result was provided for condition '{condition_key}'.") from key_error
