"""Linux preprocessor plugins."""

import csv
import datetime

from dateutil import tz

from dfvfs.helpers import text_file as dfvfs_text_file

from plaso.containers import artifacts
from plaso.lib import errors
from plaso.lib import line_reader_file
from plaso.preprocessors import interface
from plaso.preprocessors import manager


class LinuxHostnamePlugin(interface.FileArtifactPreprocessorPlugin):
    """The Linux hostname plugin."""

    ARTIFACT_DEFINITION_NAME = "LinuxHostnameFile"

    def _ParseFileData(self, mediator, file_object):
        """Parses file content (data) for a hostname 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:
          errors.PreProcessFail: if the preprocessing fails.
        """
        text_file_object = dfvfs_text_file.TextFile(file_object, encoding="utf-8")

        hostname = text_file_object.readline()
        hostname = hostname.strip()
        if hostname:
            hostname_artifact = artifacts.HostnameArtifact(name=hostname)
            mediator.AddHostname(hostname_artifact)


class LinuxDistributionPlugin(interface.FileArtifactPreprocessorPlugin):
    """The Linux distribution plugin."""

    ARTIFACT_DEFINITION_NAME = "LinuxDistributionRelease"

    def _ParseFileData(self, mediator, file_object):
        """Parses file content (data) for system product 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:
          errors.PreProcessFail: if the preprocessing fails.
        """
        text_file_object = dfvfs_text_file.TextFile(file_object, encoding="utf-8")

        system_product = text_file_object.readline()
        system_product = system_product.strip()

        if system_product:
            mediator.SetValue("operating_system_product", system_product)


class LinuxIssueFilePlugin(interface.FileArtifactPreprocessorPlugin):
    """The Linux issue file plugin."""

    ARTIFACT_DEFINITION_NAME = "LinuxIssueFile"

    def _ParseFileData(self, mediator, file_object):
        """Parses file content (data) for system product 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:
          errors.PreProcessFail: if the preprocessing fails.
        """
        text_file_object = dfvfs_text_file.TextFile(file_object, encoding="utf-8")

        system_product = text_file_object.readline()

        # Only parse known default /etc/issue file contents.
        if system_product.startswith("Debian GNU/Linux "):
            system_product, _, _ = system_product.partition("\\")
            system_product = system_product.rstrip()

        else:
            system_product = None

        if system_product:
            mediator.SetValue("operating_system_product", system_product)


class LinuxStandardBaseReleasePlugin(interface.FileArtifactPreprocessorPlugin):
    """The Linux standard base (LSB) release plugin."""

    ARTIFACT_DEFINITION_NAME = "LinuxLSBRelease"

    def _ParseFileData(self, mediator, file_object):
        """Parses file content (data) for system product 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:
          errors.PreProcessFail: if the preprocessing fails.
        """
        text_file_object = dfvfs_text_file.TextFile(file_object, encoding="utf-8")

        product_values = {}
        for line in text_file_object.readlines():
            line = line.strip()
            if line.startswith("#") or "=" not in line:
                continue
            key, _, value = line.partition("=")
            key = key.strip().upper()
            value = value.strip().strip('"')
            product_values[key] = value

        system_product = product_values.get("DISTRIB_DESCRIPTION")
        if system_product:
            mediator.SetValue("operating_system_product", system_product)


class LinuxSystemdOperatingSystemPlugin(interface.FileArtifactPreprocessorPlugin):
    """The Linux systemd operating system release plugin."""

    ARTIFACT_DEFINITION_NAME = "LinuxSystemdOSRelease"

    def _ParseFileData(self, mediator, file_object):
        """Parses file content (data) for system product 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:
          errors.PreProcessFail: if the preprocessing fails.
        """
        text_file_object = dfvfs_text_file.TextFile(file_object, encoding="utf-8")

        product_values = {}
        for line in text_file_object.readlines():
            line = line.strip()

            # Ignore lines that do not define a key value pair.
            if "=" not in line:
                continue

            key, _, value = line.partition("=")
            key = key.upper()
            value = value.strip('"')
            product_values[key] = value

        system_product = product_values.get("PRETTY_NAME")
        if system_product:
            mediator.SetValue("operating_system_product", system_product)


class LinuxTimeZonePlugin(interface.FileEntryArtifactPreprocessorPlugin):
    """Linux time zone plugin."""

    ARTIFACT_DEFINITION_NAME = "LinuxLocalTime"

    def _ParseFileEntry(self, mediator, file_entry):
        """Parses artifact file system data 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:
          errors.PreProcessFail: if the preprocessing fails.
        """
        if file_entry.link:
            # Determine the timezone based on the file path.
            _, _, time_zone = file_entry.link.partition("zoneinfo/")

        else:
            # Determine the timezone based on the timezone information file.
            file_object = file_entry.GetFileObject()

            time_zone = None
            try:
                time_zone_file = tz.tzfile(file_object)
                date_time = datetime.datetime(2017, 1, 1)
                time_zone = time_zone_file.tzname(date_time)

            except ValueError:
                mediator.ProducePreprocessingWarning(
                    self.ARTIFACT_DEFINITION_NAME,
                    "Unable to read time zone information file.",
                )

        if time_zone:
            try:
                mediator.SetTimeZone(time_zone)
            except ValueError:
                mediator.ProducePreprocessingWarning(
                    self.ARTIFACT_DEFINITION_NAME,
                    "Unable to set time zone in knowledge base.",
                )


class LinuxTimeZoneDebianPlugin(interface.FileArtifactPreprocessorPlugin):
    """Linux time zone plugin for the /etc/timezone file.

    The /etc/timezone file, used by Debian-based distributions, contains the
    IANA time zone name, for example "Europe/Zurich". This is preferred over
    the time zone abbreviation that LinuxTimeZonePlugin derives from a copied
    /etc/localtime file, for example "CET", which is ambiguous. This plugin is
    registered after LinuxTimeZonePlugin so that the IANA name, when present,
    takes precedence.
    """

    ARTIFACT_DEFINITION_NAME = "LinuxTimezoneFile"

    def _ParseFileData(self, mediator, file_object):
        """Parses file content (data) for a time zone 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:
          errors.PreProcessFail: if the preprocessing fails.
        """
        text_file_object = dfvfs_text_file.TextFile(file_object, encoding="utf-8")

        time_zone = text_file_object.readline()
        time_zone = time_zone.strip()

        if time_zone:
            try:
                mediator.SetTimeZone(time_zone)
            except ValueError:
                mediator.ProducePreprocessingWarning(
                    self.ARTIFACT_DEFINITION_NAME,
                    f"Unable to set time zone: {time_zone:s} in knowledge base.",
                )


class LinuxUserAccountsPlugin(interface.FileArtifactPreprocessorPlugin):
    """The Linux user accounts plugin."""

    ARTIFACT_DEFINITION_NAME = "LinuxPasswdFile"

    def _ParseFileData(self, mediator, file_object):
        """Parses file content (data) for user account preprocessing attributes.

        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:
          errors.PreProcessFail: if the preprocessing fails.
        """
        line_reader = line_reader_file.BinaryLineReader(file_object)

        try:
            reader = line_reader_file.BinaryDSVReader(line_reader, b":")
        except csv.Error as exception:
            raise errors.PreProcessFail(
                (
                    f"Unable to read: {self.ARTIFACT_DEFINITION_NAME:s} with error: "
                    f"{exception!s}"
                )
            )

        for line_number, row in enumerate(reader):
            if len(row) < 7 or not row[0] or not row[2]:
                mediator.ProducePreprocessingWarning(
                    self.ARTIFACT_DEFINITION_NAME,
                    f"Unsupported number of values in line: {line_number:d}.",
                )
                continue

            try:
                username = row[0].decode("utf-8")
            except UnicodeDecodeError:
                mediator.ProducePreprocessingWarning(
                    self.ARTIFACT_DEFINITION_NAME, "Unable to decode username."
                )
                continue

            try:
                identifier = row[2].decode("utf-8")
            except UnicodeDecodeError:
                mediator.ProducePreprocessingWarning(
                    self.ARTIFACT_DEFINITION_NAME, "Unable to decode user identifier."
                )
                continue

            group_identifier = None
            if row[3]:
                try:
                    group_identifier = row[3].decode("utf-8")
                except UnicodeDecodeError:
                    mediator.ProducePreprocessingWarning(
                        self.ARTIFACT_DEFINITION_NAME,
                        "Unable to decode group identifier.",
                    )

            full_name = None
            if row[4]:
                try:
                    full_name = row[4].decode("utf-8")
                except UnicodeDecodeError:
                    mediator.ProducePreprocessingWarning(
                        self.ARTIFACT_DEFINITION_NAME, "Unable to decode full name."
                    )

            user_directory = None
            if row[5]:
                try:
                    user_directory = row[5].decode("utf-8")
                except UnicodeDecodeError:
                    mediator.ProducePreprocessingWarning(
                        self.ARTIFACT_DEFINITION_NAME,
                        "Unable to decode user directory.",
                    )

            shell = None
            if row[6]:
                try:
                    shell = row[6].decode("utf-8")
                except UnicodeDecodeError:
                    mediator.ProducePreprocessingWarning(
                        self.ARTIFACT_DEFINITION_NAME, "Unable to decode shell."
                    )

            user_account = artifacts.UserAccountArtifact(
                identifier=identifier, username=username
            )
            user_account.group_identifier = group_identifier
            user_account.full_name = full_name
            user_account.user_directory = user_directory
            user_account.shell = shell

            try:
                mediator.AddUserAccount(user_account)
            except KeyError as exception:
                mediator.ProducePreprocessingWarning(
                    self.ARTIFACT_DEFINITION_NAME,
                    f"Unable to add user account with error: {exception!s}",
                )


manager.PreprocessPluginsManager.RegisterPlugins(
    [
        LinuxHostnamePlugin,
        LinuxDistributionPlugin,
        LinuxIssueFilePlugin,
        LinuxStandardBaseReleasePlugin,
        LinuxSystemdOperatingSystemPlugin,
        LinuxTimeZonePlugin,
        # LinuxTimeZoneDebianPlugin is registered after LinuxTimeZonePlugin so
        # that the IANA time zone name from /etc/timezone takes precedence over
        # the time zone abbreviation derived from a copied /etc/localtime file.
        LinuxTimeZoneDebianPlugin,
        LinuxUserAccountsPlugin,
    ]
)
