"""Parser for McAfee Anti-Virus Logs.

McAfee AV uses 4 logs to track when scans were run, when virus databases were updated,
and when files match the virus database.
"""

from dfdatetime import time_elements as dfdatetime_time_elements

from plaso.containers import events
from plaso.lib import errors
from plaso.parsers import dsv_parser
from plaso.parsers import manager


class McafeeAVEventData(events.EventData):
    """McAfee AV Log event data.

    Attributes:
      action (str): action.
      filename (str): filename.
      offset (int): offset of the line relative to the start of the file, from
          which the event data was extracted.
      rule (str): rule.
      status (str): status.
      trigger_location (str): trigger location.
      username (str): username.
      written_time (dfdatetime.DateTimeValues): entry written date and time.
    """

    DATA_TYPE = "av:mcafee:accessprotectionlog"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.action = None
        self.filename = None
        self.offset = None
        self.rule = None
        self.status = None
        self.trigger_location = None
        self.username = None
        self.written_time = None


class McafeeAccessProtectionParser(dsv_parser.DSVParser):
    """Parses the McAfee AV Access Protection Log."""

    NAME = "mcafee_protection"
    DATA_FORMAT = "McAfee Anti-Virus access protection log file"

    DELIMITER = "\t"
    COLUMNS = [
        "date",
        "time",
        "status",
        "username",
        "filename",
        "trigger_location",
        "rule",
        "action",
    ]

    _ENCODING = "utf-8"

    _NUMBER_OF_COLUMNS = len(COLUMNS)

    def _CreateDateTime(self, date_string, time_string):
        """Creates a date time value from the date time strings.

        The format stores the date and time as 2 separate strings separated by
        a tab. The time is in local time. The month and day can be either 1 or 2
        characters long, for example: "7/30/2013\\t10:22:48 AM"

        Args:
          date_string (str): date string.
          time_string (str): time string.

        Returns:
          dfdatetime.TimeElements: date time object.

        Raises:
          ParseError: if the date or time string cannot be converted in
              a date time object.
        """
        if not date_string and not time_string:
            raise errors.ParseError("Missing date or time string.")

        # TODO: Figure out how McAfee sets Day First and use that here.
        # The in-file time format is '07/30/2013\t10:22:48 AM'.

        try:
            month_string, day_of_month_string, year_string = date_string.split("/")
            year = int(year_string, 10)
            month = int(month_string, 10)
            day_of_month = int(day_of_month_string, 10)
        except (AttributeError, ValueError):
            raise errors.ParseError(f"Unsupported date string: {date_string!s}")

        try:
            time_value, time_suffix = time_string.split(" ")
            hours_string, minutes_string, seconds_string = time_value.split(":")
            hours = int(hours_string, 10)
            minutes = int(minutes_string, 10)
            seconds = int(seconds_string, 10)
        except (AttributeError, ValueError):
            raise errors.ParseError(f"Unsupported time string: {time_string!s}")

        if time_suffix == "PM":
            hours += 12
        elif time_suffix != "AM":
            raise errors.ParseError(f"Unsupported time suffix: {time_suffix!s}.")

        time_elements_tuple = (year, month, day_of_month, hours, minutes, seconds)

        try:
            date_time = dfdatetime_time_elements.TimeElements(
                time_elements_tuple=time_elements_tuple
            )
        except ValueError:
            raise errors.ParseError(
                f"Unsupported date and time strings: {date_string!s} {time_string!s}"
            )

        date_time.is_local_time = True
        return date_time

    def ParseRow(self, parser_mediator, row_offset, row):
        """Parses a line of the log file and produces events.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers
              and other components, such as storage and dfVFS.
          row_offset (int): offset of the line from which the row was extracted.
          row (dict[str, str]): fields of a single row, as specified in COLUMNS.
        """
        corrupted = False

        try:
            date_time = self._CreateDateTime(row["date"], row["time"])
        except errors.ParseError as exception:
            parser_mediator.ProduceWarning(
                f"Unable to create date time with error: {exception!s}"
            )
            date_time = None
            corrupted = True

        status = row["status"]
        if status:
            status = status.rstrip()

        event_data = McafeeAVEventData()
        event_data.action = row["action"]
        event_data.filename = row["filename"]
        event_data.offset = row_offset
        event_data.rule = row["rule"]
        event_data.status = status
        event_data.trigger_location = row["trigger_location"]
        event_data.username = row["username"]
        event_data.written_time = date_time

        parser_mediator.ProduceEventData(event_data, corrupted=corrupted)

    def VerifyRow(self, parser_mediator, row):
        """Verifies if a line of the file is in the expected format.

        Args:
          parser_mediator (ParserMediator): mediates interactions between parsers
              and other components, such as storage and dfVFS.
          row (dict[str, str]): fields of a single row, as specified in COLUMNS.

        Returns:
          bool: True if this is the correct parser, False otherwise.
        """
        if len(row) != self._NUMBER_OF_COLUMNS:
            return False

        # If the date and time string cannot be converted into a date time object, then
        # do not consider this to be a McAfee AV Access Protection Log.
        try:
            self._CreateDateTime(row["date"], row["time"])
        except errors.ParseError:
            return False

        # Use the presence of these strings as a backup or in case of partial file.
        status = row["status"]
        if "Access Protection" not in status and "Would be blocked" not in status:
            return False

        return True


manager.ParsersManager.RegisterParser(McafeeAccessProtectionParser)
