"""Analysis plugin to look up file hashes in bloom database."""

import flor  # pylint: disable=import-error

from plaso.analysis import hash_tagging
from plaso.analysis import logger
from plaso.analysis import manager


class BloomAnalysisPlugin(hash_tagging.HashTaggingAnalysisPlugin):
    """Analysis plugin for looking up hashes in bloom database."""

    DATA_TYPES = frozenset(["fs:stat", "fs:stat:ntfs"])

    NAME = "bloom"

    SUPPORTED_HASHES = frozenset(["md5", "sha1", "sha256"])

    DEFAULT_LABEL = "bloom_present"

    def __init__(self):
        """Initializes a bloom database analysis plugin."""
        super().__init__()
        self._bloom_database_path = None
        self._bloom_filter_object = None
        self._label = self.DEFAULT_LABEL

    def _Analyze(self, hashes):
        """Looks up file hashes in a bloom database.

        Args:
          hashes (list[str]): hash values to look up.

        Returns:
          list[HashAnalysis]: analysis results, or an empty list on error.

        Raises:
          RuntimeError: when the analyzer fail to get a bloom filter object.
        """
        bloom_filter = self._GetBloomFilterObject(cached=True)
        if not bloom_filter:
            raise RuntimeError("Failed to open bloom file")

        hash_analyses = []
        for digest in hashes:
            response = self._QueryHash(digest=digest, bloom_filter=bloom_filter)
            if response is not None:
                hash_analysis = hash_tagging.HashAnalysis(
                    subject_hash=digest, hash_information=response
                )
                hash_analyses.append(hash_analysis)

        return hash_analyses

    def _GenerateLabels(self, hash_information):
        """Generates a list of strings that will be used in the event tag.

        Args:
          hash_information (bool): response from the hash tagging that indicates
              that the file hash was present or not.

        Returns:
          list[str]: list of labels to apply to event.
        """
        if hash_information:
            return [self._label]
        return []

    def _GetBloomFilterObject(self, cached=True):
        """Loads a bloom filter file in memory.

        Args:
          cached (bool): True if the bloom filter should be cached.

        Returns:
          flor.BloomFilter: bloom filter object or None if not available.
        """
        bloom_filter = self._bloom_filter_object
        if not bloom_filter:
            logger.debug(f"Opening bloom database file: {self._bloom_database_path:s}.")

            if not flor:
                logger.warning("Missing optional dependency: flor")
                return None

            try:
                bloom_filter = flor.BloomFilter()
                with open(self._bloom_database_path, "rb") as file_object:
                    bloom_filter.read(file_object)

            except OSError as exception:
                bloom_filter = None
                logger.warning(
                    (
                        f"Unable to open bloom database file: "
                        f"{self._bloom_database_path:s} with error: {exception!s}."
                    )
                )

            if cached:
                self._bloom_filter_object = bloom_filter

        return bloom_filter

    def _QueryHash(self, digest, bloom_filter):
        """Queries BloomFilter for a specific hash in upper case.

        Args:
          digest (str): hash to look up.
          bloom_filter (flor.BloomFilter): instanced bloom filter.

        Returns:
          bool: True if the hash was found, False if not.
        """
        value_to_test = digest.upper().encode("utf-8")
        return value_to_test in bloom_filter

    def SetBloomDatabasePath(self, bloom_database_path):
        """Set the path to the bloom file containing hash.

        Args:
          bloom_database_path (str): Path to the bloom file
        """
        self._bloom_database_path = bloom_database_path

    def SetLabel(self, label):
        """Sets the tagging label.

        Args:
          label (str): label to apply to events extracted from files that are
              present in the bloom database.
        """
        self._label = label

    def TestLoading(self):
        """Checks if the bloom database exist and is valid.

        Returns:
          bool: True is the bloom database exist and is valid.
        """
        return bool(self._GetBloomFilterObject(cached=False))


manager.AnalysisPluginManager.RegisterPlugin(BloomAnalysisPlugin)
