"""Parser for Windows XML EventLog (EVTX) files."""

import pyevtx

from dfdatetime import filetime as dfdatetime_filetime

from plaso.containers import events
from plaso.lib import specification
from plaso.parsers import interface
from plaso.parsers import manager


class WinEvtxRecordEventData(events.EventData):
    """Windows XML EventLog (EVTX) record event data.

    Attributes:
      creation_time (dfdatetime.DateTimeValues): event record creation date
          and time.
      event_identifier (int): event identifier.
      event_level (int): event level.
      event_version (int): event version.
      hostname (str): hostname or computer name stored in the event record.
      message_identifier (int): event message identifier.
      offset (int): offset of the EVTX record relative to the start of the file,
          from which the event data was extracted.
      provider_identifier (str): identifier of the EventLog provider.
      record_number (int): event record number.
      source_name (str): name of the event source.
      strings (list[str]): event strings.
      user_sid (str): user security identifier (SID) stored in the event record.
      written_time (dfdatetime.DateTimeValues): event record written date and
          time.
      xml_string (str): XML representation of the event.
    """

    DATA_TYPE = "windows:evtx:record"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.creation_time = None
        self.event_identifier = None
        self.event_level = None
        self.event_version = None
        self.hostname = None
        self.message_identifier = None
        self.offset = None
        self.provider_identifier = None
        self.record_number = None
        self.source_name = None
        self.strings = None
        self.user_sid = None
        self.written_time = None
        self.xml_string = None


class WinEvtxParser(interface.FileObjectParser):
    """Parses Windows XML EventLog (EVTX) files."""

    _INITIAL_FILE_OFFSET = None

    NAME = "winevtx"
    DATA_FORMAT = "Windows XML EventLog (EVTX) file"

    def _ParseRecord(self, parser_mediator, record_index, evtx_record, recovered=False):
        """Extract data from a Windows XML EventLog (EVTX) record.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers and
              other components, such as storage and dfVFS.
          record_index (int): event record index.
          evtx_record (pyevtx.record): event record.
          recovered (Optional[bool]): True if the record was recovered.
        """
        corrupted = False
        event_data = WinEvtxRecordEventData()

        try:
            event_data.record_number = evtx_record.identifier
        except OverflowError as exception:
            warning_message = (
                f"unable to read record identifier from event record: {record_index:d} "
                f"with error: {exception!s}"
            )
            parser_mediator.ProduceWarning(warning_message, recovered=recovered)
            corrupted = True

        try:
            event_identifier = evtx_record.event_identifier
        except OverflowError as exception:
            warning_message = (
                f"unable to read event identifier from event record: {record_index:d} "
                f"with error: {exception!s}"
            )
            parser_mediator.ProduceWarning(warning_message, recovered=recovered)
            event_identifier = None
            corrupted = True

        try:
            event_identifier_qualifiers = evtx_record.event_identifier_qualifiers
        except OverflowError as exception:
            warning_message = (
                f"unable to read event identifier qualifiers from event record: "
                f"{record_index:d} with error: {exception!s}"
            )
            parser_mediator.ProduceWarning(warning_message, recovered=recovered)
            event_identifier_qualifiers = None
            corrupted = True

        event_data.offset = evtx_record.offset

        if event_identifier is not None:
            event_data.event_identifier = event_identifier

            event_data.message_identifier = event_identifier
            if event_identifier_qualifiers is not None:
                event_data.message_identifier |= event_identifier_qualifiers << 16

        if evtx_record.provider_identifier:
            event_data.provider_identifier = evtx_record.provider_identifier.lower()

        event_data.event_level = evtx_record.event_level
        event_data.event_version = evtx_record.event_version
        event_data.source_name = evtx_record.source_name

        # Computer name is the value stored in the event record and does not
        # necessarily correspond with the systems hostname.
        event_data.hostname = evtx_record.computer_name
        event_data.user_sid = evtx_record.user_security_identifier

        event_data.strings = list(evtx_record.strings)

        event_data.xml_string = evtx_record.xml_string

        try:
            timestamp = evtx_record.get_creation_time_as_integer()

            event_data.creation_time = dfdatetime_filetime.Filetime(timestamp=timestamp)
        except OverflowError as exception:
            warning_message = (
                f"unable to read creation time from event record: {record_index:d} "
                f"with error: {exception!s}"
            )
            parser_mediator.ProduceWarning(warning_message, recovered=recovered)
            corrupted = True

        try:
            timestamp = evtx_record.get_written_time_as_integer()

            event_data.written_time = dfdatetime_filetime.Filetime(timestamp=timestamp)
        except OverflowError as exception:
            warning_message = (
                f"unable to read written time from event record: {record_index:d} "
                f"with error: {exception!s}"
            )
            parser_mediator.ProduceWarning(warning_message, recovered=recovered)
            corrupted = True

        parser_mediator.ProduceEventData(
            event_data, corrupted=corrupted, recovered=recovered
        )

    def _ParseRecords(self, parser_mediator, evtx_file):
        """Parses Windows XML EventLog (EVTX) records.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers and
              other components, such as storage and dfVFS.
          evtx_file (pyevtx.file): Windows XML EventLog (EVTX) file.
        """
        # To handle errors when parsing a Windows XML EventLog (EVTX) file in the
        # most granular way the following code iterates over every event record.
        # The call to evt_file.get_record() and access to members of evt_record
        # should be called within a try-except.

        for record_index in range(evtx_file.number_of_records):
            if parser_mediator.abort:
                break

            try:
                evtx_record = evtx_file.get_record(record_index)
                self._ParseRecord(parser_mediator, record_index, evtx_record)

            except OSError as exception:
                warning_message = (
                    f"unable to parse event record: {record_index:d} with error: "
                    f"{exception!s}"
                )
                parser_mediator.ProduceWarning(warning_message)

        for record_index in range(evtx_file.number_of_recovered_records):
            if parser_mediator.abort:
                break

            try:
                evtx_record = evtx_file.get_recovered_record(record_index)
                self._ParseRecord(
                    parser_mediator, record_index, evtx_record, recovered=True
                )

            except OSError as exception:
                warning_message = (
                    f"unable to parse recovered event record: {record_index:d} with "
                    f"error: {exception!s}"
                )
                parser_mediator.ProduceWarning(warning_message, recovered=True)

    @classmethod
    def GetFormatSpecification(cls):
        """Retrieves the format specification.

        Returns:
          FormatSpecification: format specification.
        """
        format_specification = specification.FormatSpecification(cls.NAME)
        format_specification.AddNewSignature(b"ElfFile\x00", offset=0)
        return format_specification

    def ParseFileObject(self, parser_mediator, file_object):
        """Parses a Windows XML EventLog (EVTX) file-like object.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers and
              other components, such as storage and dfVFS.
          file_object (dfvfs.FileIO): a file-like object.
        """
        code_page = parser_mediator.GetCodePage()

        evtx_file = pyevtx.file()
        evtx_file.set_ascii_codepage(code_page)

        try:
            evtx_file.open_file_object(file_object)
        except OSError as exception:
            warning_message = f"unable to open file with error: {exception!s}"
            parser_mediator.ProduceWarning(warning_message)
            return

        try:
            self._ParseRecords(parser_mediator, evtx_file)
        finally:
            evtx_file.close()


manager.ParsersManager.RegisterParser(WinEvtxParser)
