"""Shared functionality for JSON based output modules."""

import abc
import base64

from acstore.containers import interface as containers_interface

from dfdatetime import interface as dfdatetime_interface

from plaso.output import formatting_helper
from plaso.output import text_file
from plaso.serializer import json_serializer


class JSONFieldFormattingHelper(formatting_helper.FieldFormattingHelper):
    """JSON output module field formatting helper."""

    # Maps the name of a fields to a a callback function that formats the field value.
    _FIELD_FORMAT_CALLBACKS = {
        "display_name": "_FormatDisplayName",
        "filename": "_FormatFilename",
        "inode": "_FormatInode",
        "message": "_FormatMessage",
        "values": "_FormatValues",
    }

    # The field format callback methods require specific arguments hence the check for
    # unused arguments is disabled here.
    # pylint: disable=unused-argument

    def _FormatValues(self, output_mediator, event, event_data, event_data_stream):
        """Formats a values.

        Args:
          output_mediator (OutputMediator): mediates interactions between output
              modules and other components, such as storage and dfVFS.
          event (EventObject): event.
          event_data (EventData): event data.
          event_data_stream (EventDataStream): event data stream.

        Returns:
          list[dict[str, str]]: values field.
        """
        if isinstance(event_data.values, list) and event_data.data_type in (
            "windows:registry:key_value",
            "windows:registry:service",
        ):
            values = []
            for name, data_type, data in sorted(event_data.values):
                if isinstance(data, bytes):
                    byte_stream = base64.urlsafe_b64encode(data)
                    data = {
                        "__encoding__": "base64url",
                        "__type__": "bytes",
                        "stream": byte_stream.decode("ascii"),
                    }

                value_dict = {"data": data, "data_type": data_type, "name": name}
                values.append(value_dict)

        else:
            values = event_data.values

        return values

    # pylint: enable=unused-argument

    def GetFormattedField(
        self,
        output_mediator,
        field_name,
        event,
        event_data,
        event_data_stream,
        event_tag,
    ):
        """Formats the specified field.

        Args:
          output_mediator (OutputMediator): mediates interactions between output
              modules and other components, such as storage and dfVFS.
          field_name (str): name of the field.
          event (EventObject): event.
          event_data (EventData): event data.
          event_data_stream (EventDataStream): event data stream.
          event_tag (EventTag): event tag.

        Returns:
          object: value of the field or None if not available.
        """
        if field_name in self._event_tag_field_names:
            return self._FormatTag(output_mediator, event_tag)

        callback_function = self._callback_functions.get(field_name)
        if callback_function:
            output_value = callback_function(
                output_mediator, event, event_data, event_data_stream
            )
        elif field_name in self._event_data_stream_field_names:
            output_value = getattr(event_data_stream, field_name, None)
        else:
            output_value = getattr(event_data, field_name, None)

        return output_value


class SharedJSONOutputModule(text_file.TextFileOutputModule):
    """Shared functionality for JSON based output modules."""

    _JSON_SERIALIZER = json_serializer.JSONAttributeContainerSerializer

    def __init__(self):
        """Initializes an output module."""
        super().__init__()
        self._field_formatting_helper = JSONFieldFormattingHelper()
        self._generated_field_values = None

    def GetFieldValues(
        self, output_mediator, event, event_data, event_data_stream, event_tag
    ):
        """Retrieves the output field values.

        Args:
          output_mediator (OutputMediator): mediates interactions between output
              modules and other components, such as storage and dfVFS.
          event (EventObject): event.
          event_data (EventData): event data.
          event_data_stream (EventDataStream): event data stream.
          event_tag (EventTag): event tag.

        Returns:
          dict[str, str]: output field values per name.
        """
        if self._generated_field_values is None:
            self._generated_field_values = ["display_name"]
            if output_mediator.use_fallback_path_spec:
                self._generated_field_values.extend(["filename", "inode"])

        field_values = {"__container_type__": "event", "__type__": "AttributeContainer"}

        if event_data:
            for attribute_name, attribute_value in event_data.GetAttributes():
                # Ignore attribute container identifier and date and time values.
                if isinstance(
                    attribute_value,
                    (
                        containers_interface.AttributeContainerIdentifier,
                        dfdatetime_interface.DateTimeValues,
                    ),
                ):
                    continue

                if (
                    isinstance(attribute_value, list)
                    and attribute_value
                    and isinstance(
                        attribute_value[0], dfdatetime_interface.DateTimeValues
                    )
                ):
                    continue

                # Ignore protected internal only attributes.
                if attribute_name[0] == "_" and attribute_name != "_parser_chain":
                    continue

                field_value = self._field_formatting_helper.GetFormattedField(
                    output_mediator,
                    attribute_name,
                    event,
                    event_data,
                    event_data_stream,
                    event_tag,
                )
                # Output _parser_chain as parser for backwards compatibility.
                if attribute_name == "_parser_chain":
                    attribute_name = "parser"

                field_values[attribute_name] = field_value

        if event_data_stream:
            for attribute_name, attribute_value in event_data_stream.GetAttributes():
                # Output path_spec as pathspec for backwards compatibility.
                if attribute_name == "path_spec":
                    attribute_name = "pathspec"
                    attribute_value = self._JSON_SERIALIZER.WriteSerializedDict(
                        attribute_value
                    )

                field_values[attribute_name] = attribute_value

        if event:
            for attribute_name, attribute_value in event.GetAttributes():
                # Ignore attribute container identifier values.
                if isinstance(
                    attribute_value, containers_interface.AttributeContainerIdentifier
                ):
                    continue

                if attribute_name == "date_time":
                    attribute_value = self._JSON_SERIALIZER.WriteSerializedDict(
                        attribute_value
                    )

                field_values[attribute_name] = attribute_value

        for field_name in self._generated_field_values:
            if field_name not in field_values:
                field_value = field_values.get(field_name)
                if field_value is None:
                    field_value = self._field_formatting_helper.GetFormattedField(
                        output_mediator,
                        field_name,
                        event,
                        event_data,
                        event_data_stream,
                        event_tag,
                    )
                    field_values[field_name] = field_value

        field_values["message"] = self._field_formatting_helper.GetFormattedField(
            output_mediator, "message", event, event_data, event_data_stream, event_tag
        )
        if event_tag:
            event_tag_values = {
                "__container_type__": "event_tag",
                "__type__": "AttributeContainer",
            }
            for attribute_name, attribute_value in event_tag.GetAttributes():
                # Ignore attribute container identifier values.
                if isinstance(
                    attribute_value, containers_interface.AttributeContainerIdentifier
                ):
                    continue

                event_tag_values[attribute_name] = attribute_value

            field_values["tag"] = event_tag_values

        return field_values

    @abc.abstractmethod
    def WriteFieldValues(self, output_mediator, field_values):
        """Writes field values to the output.

        Args:
          output_mediator (OutputMediator): mediates interactions between output
              modules and other components, such as storage and dfVFS.
          field_values (dict[str, str]): output field values per name.
        """
