from collections import defaultdict, Counter
from constants import compare, RE_THRESHOLD, RE_DELIMITERS
from utils import list_get
import logging
import re
import settings


class RulesManager(object):
    """
    To manage all of the rules. A rule is a single description centered around a metric.  A rule can have a part denoted
    by an underscore followed by a part value (it is useful to use a numerical part value).  Multiple rule parts are
    automatically combined by having the same rule name.
    A single rule is a dict object:
    {
        "{{ prefix__rule_name__rule_part }}": {
            "ruleInfo": {
                "ruleName": "{{rule_name}}",
                "rulePart": "{{rule_part}}"
            },
            {
            "ruleValue": {
                "metric",
                "threshold": {op, val},
                "backoff",
                "scale_factor",
                "tolerance",
                "weight"
            }
        }
    }
    """
    def __init__(self, app_def, logger=None):
        self.logger = logger or logging.getLogger(__name__)
        self.app_def = app_def
        self.rules = self._find_autoscaler_rules()
        self._last_triggered_rule = None

    def trigger_rules(self, metrics):
        """
        Consumes a dict of metrics and attempts to match an application's rules to its metric information.
        :param metrics: dict of metric values
        :return: The recently triggered rule
        """

        triggered_rule = self._get_matched_rule(metrics)

        self._last_triggered_rule = triggered_rule
        info_msg = "{app_name}: metrics: {metrics}"
        self.logger.info(info_msg.format(app_name=self.app_def.app_name,
                                         metrics=metrics))
        info_msg = "{app_name}: last_triggered_rule set to: {triggered_rule}"
        self.logger.info(info_msg.format(app_name=self.app_def.app_name,
                                         triggered_rule=triggered_rule))
        return self._last_triggered_rule

    @property
    def last_triggered_rule(self):
        """
        The last rule triggered by the RuleManager
        :return:
        """
        triggered_rule = None
        if self._last_triggered_rule is not None:
            triggered_rule = self._last_triggered_rule
        return triggered_rule

    @property
    def last_triggered_criteria(self):
        """
        A helper property to aim at providing the core criteria of the last triggered rule.
        :return: A dict of rule criteria
        """
        criteria = {}
        if self._last_triggered_rule is not None:
            rule_value = self._last_triggered_rule[0].get("ruleValue")
            criteria = dict(scale_factor=rule_value.get("scale_factor"),
                            tolerance=rule_value.get("tolerance"),
                            backoff=rule_value.get("backoff"))
        return criteria

    def _find_autoscaler_rules(self):
        rules_found = {}
        if self.app_def.labels:
            for k, v in self.app_def.labels.items():
                rule_match = re.match(r"^{prefix}_(?P<ruleName>[A-Za-z0-9]+)_?(?P<rulePart>[A-Za-z0-9]+)*".format(
                    prefix=settings.rules_prefix), k)
                if rule_match is not None:
                    rule_values = re.split(RE_DELIMITERS, v)
                    if len(rule_values) < 5:
                        self.logger.warn("Scaling rule identified, but wrong number of arguments. Disregarding "
                                         "{rule_name} = {rule_values}".format(rule_name=k,
                                                                              rule_values=rule_values))
                        continue

                    rule_values_dict = dict(metric=rule_values[0],
                                            threshold=self._parse_threshold(rule_values[1]),
                                            tolerance=rule_values[2],
                                            scale_factor=rule_values[3],
                                            backoff=rule_values[4],
                                            weight=list_get(rule_values, 5, 1.0))