"""This file contains classes used for preprocessing in plaso."""

import abc

from dfwinreg import registry_searcher

from artifacts import definitions as artifact_definitions
from dfvfs.helpers import file_system_searcher

from plaso.lib import errors


class ArtifactPreprocessorPlugin:
    """The artifact preprocessor plugin interface.

    The artifact preprocessor determines preprocessing attributes based on an artifact
    definition defined by ARTIFACT_DEFINITION_NAME.
    """

    ARTIFACT_DEFINITION_NAME = None


class FileSystemArtifactPreprocessorPlugin(ArtifactPreprocessorPlugin):
    """File system artifact preprocessor plugin interface.

    Shared functionality for preprocessing attributes based on a file system artifact
    definition, such as file or path.
    """

    @abc.abstractmethod
    def _ParsePathSpecification(
        self, mediator, searcher, file_system, path_specification, path_separator
    ):
        """Parses a file system for a preprocessing attribute.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.
          searcher (dfvfs.FileSystemSearcher): file system searcher to preprocess
              the file system.
          file_system (dfvfs.FileSystem): file system to be preprocessed.
          path_specification (dfvfs.PathSpec): path specification that contains
              the artifact value data.
          path_separator (str): path segment separator.

        Raises:
          PreProcessFail: if the preprocessing fails.
        """

    def Collect(self, mediator, artifact_definition, searcher, file_system):
        """Collects values using a file artifact definition.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.
          artifact_definition (artifacts.ArtifactDefinition): artifact definition.
          searcher (dfvfs.FileSystemSearcher): file system searcher to preprocess
              the file system.
          file_system (dfvfs.FileSystem): file system to be preprocessed.

        Raises:
          PreProcessFail: if the preprocessing fails.
        """
        last_exception = None

        for source in artifact_definition.sources:
            if source.type_indicator not in (
                artifact_definitions.TYPE_INDICATOR_FILE,
                artifact_definitions.TYPE_INDICATOR_PATH,
            ):
                continue

            for path in source.paths:
                find_spec = file_system_searcher.FindSpec(
                    case_sensitive=False,
                    location_glob=path,
                    location_separator=source.separator,
                )
                for path_specification in searcher.Find(find_specs=[find_spec]):
                    try:
                        self._ParsePathSpecification(
                            mediator,
                            searcher,
                            file_system,
                            path_specification,
                            source.separator,
                        )
                        last_exception = None

                    except errors.PreProcessFail as exception:
                        last_exception = exception

        if last_exception:
            # Only raise an exception if none of the sources were successfully
            # pre-processed.
            raise last_exception


class FileEntryArtifactPreprocessorPlugin(FileSystemArtifactPreprocessorPlugin):
    """File entry artifact preprocessor plugin interface.

    Shared functionality for preprocessing attributes based on a file entry artifact
    definition, such as file or path.
    """

    @abc.abstractmethod
    def _ParseFileEntry(self, mediator, file_entry):
        """Parses a file entry for a preprocessing attribute.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.
          file_entry (dfvfs.FileEntry): file entry that contains the artifact
              value data.

        Raises:
          PreProcessFail: if the preprocessing fails.
        """

    def _ParsePathSpecification(
        self, mediator, searcher, file_system, path_specification, path_separator
    ):
        """Parses a file system for a preprocessing attribute.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.
          searcher (dfvfs.FileSystemSearcher): file system searcher to preprocess
              the file system.
          file_system (dfvfs.FileSystem): file system to be preprocessed.
          path_specification (dfvfs.PathSpec): path specification that contains
              the artifact value data.
          path_separator (str): path segment separator.

        Raises:
          PreProcessFail: if the preprocessing fails.
        """
        try:
            file_entry = searcher.GetFileEntryByPathSpec(path_specification)
        except OSError as exception:
            relative_path = searcher.GetRelativePath(path_specification)
            if path_separator != file_system.PATH_SEPARATOR:
                relative_path_segments = file_system.SplitPath(relative_path)
                relative_path = path_separator.join(relative_path_segments)
                relative_path = "".join([path_separator, relative_path])

            raise errors.PreProcessFail(
                f"Unable to retrieve file entry: {relative_path:s} with error: "
                f"{exception!s}"
            )

        if file_entry:
            mediator.SetFileEntry(file_entry)
            self._ParseFileEntry(mediator, file_entry)


class FileArtifactPreprocessorPlugin(FileEntryArtifactPreprocessorPlugin):
    """File artifact preprocessor plugin interface.

    Shared functionality for preprocessing attributes based on a file artifact
    definition, such as file or path.
    """

    @abc.abstractmethod
    def _ParseFileData(self, mediator, file_object):
        """Parses file content (data) for a preprocessing attribute.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.
          file_object (dfvfs.FileIO): file-like object that contains the artifact
              value data.

        Raises:
          PreProcessFail: if the preprocessing fails.
        """

    def _ParseFileEntry(self, mediator, file_entry):
        """Parses a file entry for a preprocessing attribute.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.
          file_entry (dfvfs.FileEntry): file entry that contains the artifact
              value data.

        Raises:
          PreProcessFail: if the preprocessing fails.
        """
        if not file_entry.IsFile():
            raise errors.PreProcessFail(
                f"Unable to read: {self.ARTIFACT_DEFINITION_NAME:s} with error: "
                f"file entry is not a regular file"
            )

        file_object = file_entry.GetFileObject()
        self._ParseFileData(mediator, file_object)


class WindowsRegistryKeyArtifactPreprocessorPlugin(ArtifactPreprocessorPlugin):
    """Windows Registry key artifact preprocessor plugin interface.

    Shared functionality for preprocessing attributes based on a Windows Registry
    artifact definition, such as Windows Registry key or value.
    """

    @abc.abstractmethod
    def _ParseKey(self, mediator, registry_key, value_name):
        """Parses a Windows Registry key for a preprocessing attribute.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.
          registry_key (dfwinreg.WinRegistryKey): Windows Registry key.
          value_name (str): name of the Windows Registry value or None if not
              specified.

        Raises:
          PreProcessFail: if the preprocessing fails.
        """

    def Collect(self, mediator, artifact_definition, searcher):
        """Collects values using a Windows Registry value artifact definition.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.
          artifact_definition (artifacts.ArtifactDefinition): artifact definition.
          searcher (dfwinreg.WinRegistrySearcher): Windows Registry searcher to
              preprocess the Windows Registry.

        Raises:
          PreProcessFail: if the Windows Registry key or value cannot be read.
        """
        for source in artifact_definition.sources:
            if source.type_indicator not in (
                artifact_definitions.TYPE_INDICATOR_WINDOWS_REGISTRY_KEY,
                artifact_definitions.TYPE_INDICATOR_WINDOWS_REGISTRY_VALUE,
            ):
                continue

            if source.type_indicator == (
                artifact_definitions.TYPE_INDICATOR_WINDOWS_REGISTRY_KEY
            ):
                key_value_pairs = [{"key": key} for key in source.keys]
            else:
                key_value_pairs = source.key_value_pairs

            for key_value_pair in key_value_pairs:
                key_path = key_value_pair["key"]

                # The artifact definitions currently incorrectly define
                # CurrentControlSet so we correct it here for now.
                # Also see: https://github.com/ForensicArtifacts/artifacts/issues/120
                key_path_upper = key_path.upper()
                if key_path_upper.startswith("%%CURRENT_CONTROL_SET%%"):
                    key_path = "".join(
                        ["HKEY_LOCAL_MACHINE\\System\\CurrentControlSet", key_path[23:]]
                    )

                find_spec = registry_searcher.FindSpec(key_path_glob=key_path)

                for key_path in searcher.Find(find_specs=[find_spec]):
                    try:
                        registry_key = searcher.GetKeyByPath(key_path)
                    except OSError as exception:
                        raise errors.PreProcessFail(
                            f"Unable to retrieve Windows Registry key: {key_path:s} "
                            f"with error: {exception!s}"
                        )

                    if registry_key:
                        value_name = key_value_pair.get("value")
                        self._ParseKey(mediator, registry_key, value_name)


class WindowsRegistryValueArtifactPreprocessorPlugin(
    WindowsRegistryKeyArtifactPreprocessorPlugin
):
    """Windows Registry value artifact preprocessor plugin interface.

    Shared functionality for preprocessing attributes based on a Windows Registry value
    artifact definition.
    """

    def _ParseKey(self, mediator, registry_key, value_name):
        """Parses a Windows Registry key for a preprocessing attribute.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.
          registry_key (dfwinreg.WinRegistryKey): Windows Registry key.
          value_name (str): name of the Windows Registry value or None if not
              specified.

        Raises:
          PreProcessFail: if the preprocessing fails.
        """
        try:
            registry_value = registry_key.GetValueByName(value_name)
        except OSError as exception:
            raise errors.PreProcessFail(
                f"Unable to retrieve Windows Registry key: {registry_key.path:s} "
                f"value: {value_name:s} with error: {exception!s}"
            )

        if registry_value:
            value_object = registry_value.GetDataAsObject()
            if value_object:
                self._ParseValueData(mediator, value_object)

    @abc.abstractmethod
    def _ParseValueData(self, mediator, value_data):
        """Parses Windows Registry value data for a preprocessing attribute.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.
          value_data (object): Windows Registry value data.

        Raises:
          PreProcessFail: if the preprocessing fails.
        """


# TODO: rename class.
class KnowledgeBasePreprocessorPlugin:
    """The knowledge base preprocessor plugin interface.

    The knowledge base preprocessor determines preprocessing attributes based on other
    values in the knowledge base.
    """

    @abc.abstractmethod
    def Collect(self, mediator):
        """Collects values from the knowledge base.

        Args:
          mediator (PreprocessMediator): mediates interactions between preprocess
              plugins and other components, such as storage.

        Raises:
          PreProcessFail: if the preprocessing fails.
        """
