# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Utility class to manage persistent compliance statistics.

This is a file summarizing the compliance statistics per directive,
one line per directive, each line formatted as follows:

    timestamp X/Y/Z id.version name ...

where:

    - timestamp is a decimal float giving skew-corrected UTC time
    - X is the total number of rules executed in the directive
    - Y is the number of rules reporting compliance
    - Z is the number of rules reporting N/A
    - id.version is the directive id and version
    - name ... is the directive name (may contain spaces)

Note that the number of rules reporting a violation is X - Y - Z.

"""

import os
import re
import time
import logging

from sigma.util import timeskew, xmlparse

logger = logging.getLogger("util.compliance")

class Compliance(object):

    def __init__(self, options):
        self.options = options
        self.filename = options.datapath("compliance.txt")
        self.skew = None # initialized to get_skew(self.options) when needed
        self.load()

    pattern = r"(\d+(?:\.\d*))\s+(\d+)/(\d+)/(\d+)\s+(\S+)\s+(\S.*)"
    regex = re.compile(pattern)

    def load(self):
        self.records = {}
        if os.path.exists(self.filename):
            try:
                f = open(self.filename)
            except IOError as err:
                logger.error("Can't open %s: %s", self.filename, err)
            else:
                try:
                    for i, line in enumerate(f):
                        m = self.regex.match(line)
                        if m is None:
                            logger.warning("Can't parse line %d of %s: %r",
                                        i+1, self.filename, line)
                        else:
                            ts, x, y, z, key, name = m.groups()
                            self.records[key] = (float(ts),
                                                 int(x), int(y), int(z),
                                                 name)
                finally:
                    f.close()

    def save(self):
        try:
            f = open(self.filename, "w")
        except IOError as err:
            logger.error("Can't write %s: %s", self.filename, err)
        else:
            try:
                L = [(value, key) for key, value in self.records.items()]
                L.sort()
                for (ts, x, y, z, name), key in L:
                    f.write("%.3f %d/%d/%d %s %s\n" % (ts, x, y, z, key, name))
            finally:
                f.close()

    def get(self, key):
        return self.records.get(key)

    def set(self, key, x, y, z, name):
        x, y, z = map(int, (x, y, z))
        if self.skew is None:
            self.skew = timeskew.get_skew(self.options)
        ts = time.time() + self.skew
        self.records[key] = (ts, x, y, z, name)

    def list(self):
        return list(self.records.keys())

    def delete(self, key):
        del self.records[key]
