"""Parser for fish history files."""

import os
import re

import yaml

from dfdatetime import posix_time as dfdatetime_posix_time

from dfvfs.helpers import text_file

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


class FishHistoryEventData(events.EventData):
    """Fish history log event data.

    Attributes:
      command (str): command that was executed.
      written_time (dfdatetime.DateTimeValues): date and time the entry was written.
    """

    DATA_TYPE = "fish:history:entry"

    def __init__(self):
        """Initializes event data."""
        super().__init__(data_type=self.DATA_TYPE)
        self.command = None
        self.written_time = None


class FishHistoryParser(interface.FileObjectParser):
    """Parses events from Fish history files."""

    NAME = "fish_history"
    DATA_FORMAT = "Fish history file"

    _ENCODING = "utf-8"

    # 50 MiB is the maximum supported fish history file size.
    _MAXIMUM_FISH_HISTORY_FILE_SIZE = 50 * 1024 * 1024

    _FILENAME = "fish_history"

    _YAML_FORMAT_RE_1 = re.compile(r"^- cmd: \S+")
    _YAML_FORMAT_RE_2 = re.compile(r"  when: [0-9]{9}")

    def ParseFileObject(self, parser_mediator, file_object):
        """Parses a fish history file from a 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.

        Raises:
          WrongParser: when the file cannot be parsed.
        """
        filename = parser_mediator.GetFilename()

        if filename != self._FILENAME:
            raise errors.WrongParser("Not a Fish history file.")

        text_file_object = text_file.TextFile(file_object, encoding="utf-8")

        header_line_1 = text_file_object.readline()
        header_line_2 = text_file_object.readline()
        if not self._YAML_FORMAT_RE_1.match(
            header_line_1
        ) or not self._YAML_FORMAT_RE_2.match(header_line_2):
            raise errors.WrongParser("Not a valid fish history file.")

        file_size = file_object.get_size()
        if file_size > self._MAXIMUM_FISH_HISTORY_FILE_SIZE:
            parser_mediator.ProduceWarning(
                f"Fish history file size: {file_size:d} exceeds maxmimum"
            )
            return

        file_object.seek(0, os.SEEK_SET)

        try:
            fish_history = yaml.safe_load(file_object)
        except yaml.YAMLError as exception:
            parser_mediator.ProduceWarning(
                f"Error reading YAML with error: {exception:s}"
            )
            return

        for entry_index, history_entry in enumerate(fish_history):
            date_time = None
            corrupted = True

            timestamp = history_entry.get("when")
            if timestamp is None:
                parser_mediator.ProduceWarning(
                    f"Missing 'when' value in entry: {entry_index:d}"
                )
                corrupted = True
            else:
                try:
                    timestamp = int(timestamp)
                    date_time = dfdatetime_posix_time.PosixTime(timestamp=timestamp)
                except (TypeError, ValueError):
                    parser_mediator.ProduceWarning(
                        f"Unsupported timestamp: {timestamp!s} in entry: "
                        f"{entry_index:s}"
                    )
                    corrupted = True

            command = history_entry.get("cmd")
            if command is None:
                parser_mediator.ProduceWarning(
                    f"Missing 'cmd' value in entry: {entry_index:d}"
                )
                corrupted = True

            event_data = FishHistoryEventData()
            event_data.command = command
            event_data.written_time = date_time

            parser_mediator.ProduceEventData(event_data, corrupted=corrupted)


manager.ParsersManager.RegisterParser(FishHistoryParser)
