#
#   logactio  -  simple framework for doing configured action on certain
#                log file events
#
# SPDX-FileCopyrightText: Copyright 2012-  David Sommerseth <dazo@eurephia.org>
# SPDX-License-Identifier: GPL-2.0-only
#

import hashlib
import time


class ThresholdType_Rule:
    def __init__(self, params):
        if "threshold" not in params:
            raise ValueError("Missing required 'threshold' parameter")

        self.__threshold = int(params["threshold"])
        self.__timeframe = int(params["timeframe"]) if params["timeframe"] else None
        self.__ratelimit = int(params["ratelimit"]) if params["ratelimit"] else None
        self.__lastseen = 0
        self.__lastsent = 0
        self.__currentcount = 0

    def CheckThreshold(self, alert, notused_regexmatch):
        now = int(time.time())
        self.__currentcount += 1
        ret = (self.__threshold == 0
                or ((self.__currentcount % self.__threshold == 0)
                    and (self.__timeframe is None
                         or now <= (self.__lastseen + self.__timeframe)))
                and (self.__ratelimit is None or now > (self.__lastsent + self.__ratelimit)))

        if (self.__timeframe and (self.__lastseen > 0)
            and (now >= (self.__lastseen + self.__timeframe))):
            # If the time-frame have timed out, reset it
            self.__lastseen = 0
        else:
            self.__lastseen = now

        return ret


    def ClearTimeTrackers(self, notused_regexmatch):
        self.__lastseen = 0
        self.__lastsent = 0


    def GetThreshold(self):
        return self.__threshold


    def GetCurrentCount(self, rgmatch):
        return self.__currentcount



class ThresholdType_Exact:
    def __init__(self, params):
        self.__matchedgroups = {}

        self.__threshold = int(params["threshold"])
        self.__timeframe = int(params["timeframe"]) if params["timeframe"] else None
        self.__ratelimit = int(params["ratelimit"]) if params["ratelimit"] else None

    def __gen_hash(self, regexmatch):
        v = "|".join(regexmatch)
        return hashlib.sha384(v.encode('utf-8')).hexdigest()


    def CheckThreshold(self, alert, regexmatch):
        now = int(time.time())
        ret = False

        # If threshold is 0 or 1, then we process all records
        if self.__threshold < 2:
            return True

        # Check if we have a regexmatch on this from earlier checks
        rghash = self.__gen_hash(regexmatch)
        if rghash in self.__matchedgroups:
            lastevent = self.__matchedgroups[rghash]
            lastevent["count"] += 1

            ret = ((lastevent["count"] % self.__threshold == 0)
                   and (self.__timeframe is None
                        or now <= (lastevent["lastseen"] + self.__timeframe))
                   and (self.__ratelimit is None or now > (lastevent["lastsent"] + self.__ratelimit)))

            try:
                if (self.__timeframe and 'lastseen' in lastevent
                    and (lastevent["lastseen"] > 0)
                    and (now >= (lastevent["lastseen"] + self.__timeframe))):
                    # If the time-frame have timed out, reset it
                    self.__lastseen = 0
                else:
                    self.__lastseen = now
            except KeyError:
                self.__lastseen = now

        else:
            # Not seen before, register it as a new one
            self.__matchedgroups[rghash] = {
                "count": 1,
                "lastseen": now,
                "lastsent": 0
                }
            ret = (1 % self.__threshold == 0)

        return ret


    def ClearTimeTrackers(self, regexmatch):
        rghash = self.__gen_hash(regexmatch)
        if rghash in self.__matchedgroups:
            self.__matchedgroups[rghash]["lasteen"] = 0
            self.__matchedgroups[rghash]["lastsent"] = 0


    def GetThreshold(self):
        return self.__threshold


    def GetCurrentCount(self, regexmatch):
        rghash = self.__gen_hash(regexmatch)
        if rghash in self.__matchedgroups:
            return self.__matchedgroups[rghash]["count"]
        else:
            return 0



class ThresholdWatch:
    WATCHTYPE_RULE = 1
    WATCHTYPE_EXACT = 2

    def __init__(self, watchtype, params):
        self.__watchtype = watchtype
        if watchtype == self.WATCHTYPE_RULE:
            self.__thresholdtype = ThresholdType_Rule(params)
        elif watchtype == self.WATCHTYPE_EXACT:
            self.__thresholdtype = ThresholdType_Exact(params)
        else:
            raise ValueError("Invalid watchtype parameter")


    def CheckThreshold(self, alert, regexmatch):
        return self.__thresholdtype.CheckThreshold(alert, regexmatch)


    def ClearTimeTrackers(self, regexmatch):
        self.__thresholdtype.ClearTimeTrackers(regexmatch)


    def GetThreshold(self):
        return self.__thresholdtype.GetThreshold()


    def GetCurrentCount(self, regexmatch):
        return self.__thresholdtype.GetCurrentCount(regexmatch)

