"""Plist parser plugin for Mac OS login items plist files."""

import os

from dfdatetime import hfs_time as dfdatetime_hfs_time

from dtfabric.runtime import data_maps as dtfabric_data_maps

from plaso.containers import events
from plaso.lib import dtfabric_helper
from plaso.lib import errors
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface


class MacOSLoginItemEventData(events.EventData):
    """Mac OS login item event data.

    Attributes:
      hidden (bool): whether this login item is hidden.
      name (str): name.
      target_creation_time (dfdatetime.DateTimeValues): date and time the target
          was created.
      target_path (str): path of the target.
      volume_creation_time (dfdatetime.DateTimeValues): date and time the (target)
          volume was created.
      volume_flags (int): volume flags.
      volume_mount_point (str): location the volume is mounted on the file system.
      volume_name (str): name of the volume containing the target.
    """

    DATA_TYPE = "macos:login_items:entry"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.hidden = None
        self.name = None
        self.target_creation_time = None
        self.target_path = None
        self.volume_creation_time = None
        self.volume_flags = None
        self.volume_mount_point = None
        self.volume_name = None


class MacOSLoginItemsPlistPlugin(interface.PlistPlugin, dtfabric_helper.DtFabricHelper):
    """Plist parser plugin for Mac OS login items."""

    NAME = "macos_login_items_plist"
    DATA_FORMAT = "Mac OS com.apple.loginitems.plist file"

    PLIST_PATH_FILTERS = frozenset(
        [interface.PlistPathFilter("com.apple.loginitems.plist")]
    )

    PLIST_KEYS = frozenset(["SessionItems"])

    _DEFINITION_FILE = os.path.join(os.path.dirname(__file__), "alias_data.yaml")

    def _ParseAliasData(self, alias_data, event_data):
        """Parses alias data.

        Args:
          alias_data (bytes): alias data.
          event_data (MacOSLoginItemEventData): event data.

        Raises:
          ParseError: if the value cannot be parsed.
        """
        data_type_map = self._GetDataTypeMap("alias_data_record_header")

        record_header = self._ReadStructureFromByteStream(alias_data, 0, data_type_map)
        data_offset = 8

        if record_header.application_information != b"\x00\x00\x00\x00":
            raise errors.ParseError("Unsupported alias application information")

        if record_header.record_size != len(alias_data):
            raise errors.ParseError("Unsupported alias record size")

        # TODO: add format version 2 support, but need test data.
        data_type_map = self._GetDataTypeMap("alias_data_record_v3")

        record_data = self._ReadStructureFromByteStream(
            alias_data[data_offset:], data_offset, data_type_map
        )
        data_offset += 50

        hfs_timestamp, _ = divmod(record_data.target_creation_time, 65536)
        event_data.target_creation_time = dfdatetime_hfs_time.HFSTime(
            timestamp=hfs_timestamp
        )

        event_data.volume_flags = record_data.volume_flags

        hfs_timestamp, _ = divmod(record_data.volume_creation_time, 65536)
        event_data.volume_creation_time = dfdatetime_hfs_time.HFSTime(
            timestamp=hfs_timestamp
        )

        relative_target_path = None

        while data_offset < record_header.record_size:
            data_type_map = self._GetDataTypeMap("alias_data_tagged_value")

            context = dtfabric_data_maps.DataTypeMapContext()

            tagged_value = self._ReadStructureFromByteStream(
                alias_data[data_offset:], data_offset, data_type_map, context=context
            )
            data_offset += context.byte_size

            if tagged_value.value_tag == 0xFFFF:
                break

            if tagged_value.value_tag == 0x000F:
                event_data.volume_name = tagged_value.string

            elif tagged_value.value_tag == 0x0012:
                relative_target_path = tagged_value.string

            elif tagged_value.value_tag == 0x0013:
                volume_mount_point = tagged_value.string
                if relative_target_path:
                    relative_target_path = "".join(
                        [volume_mount_point, relative_target_path]
                    )

                event_data.volume_mount_point = volume_mount_point

        if relative_target_path:
            event_data.target_path = relative_target_path

    # pylint: disable=arguments-differ
    def _ParsePlist(self, parser_mediator, top_level=None, **unused_kwargs):
        """Extracts login item information from the plist.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers
              and other components, such as storage and dfVFS.
          top_level (Optional[dict[str, object]]): plist top-level item.
        """
        session_items = top_level.get("SessionItems")
        if not session_items:
            return

        for custom_list_item in session_items.get("CustomListItems"):
            alias_data = custom_list_item.get("Alias")
            properties = custom_list_item.get("CustomItemProperties", {})

            event_data = MacOSLoginItemEventData()
            event_data.name = custom_list_item.get("Name")
            event_data.hidden = properties.get(
                "com.apple.LSSharedFileList.ItemIsHidden", False
            )

            if alias_data is not None:
                self._ParseAliasData(alias_data, event_data)

            parser_mediator.ProduceEventData(event_data)


plist.PlistParser.RegisterPlugin(MacOSLoginItemsPlistPlugin)
