"""Shared functionality for an analysis CLI tool."""

import time

from dfdatetime import posix_time as dfdatetime_posix_time

from plaso.analysis import manager as analysis_manager
from plaso.cli import tool_options
from plaso.cli import tools
from plaso.cli import views
from plaso.containers import reports
from plaso.multi_process import analysis_engine as multi_analysis_engine
from plaso.storage import factory as storage_factory


class AnalysisTool(
    tools.CLITool,
    tool_options.AnalysisPluginOptions,
    tool_options.ProfilingOptions,
    tool_options.StorageFileOptions,
):
    """Analysis CLI tool.

    Attributes:
      list_analysis_plugins (bool): True if information about the analysis
          plugins should be shown.
    """

    _CONTAINER_TYPE_ANALYSIS_REPORT = reports.AnalysisReport.CONTAINER_TYPE

    def __init__(self, input_reader=None, output_writer=None):
        """Initializes the CLI tool object.

        Args:
          input_reader (Optional[InputReader]): input reader, where None indicates
              that the stdin input reader should be used.
          output_writer (Optional[OutputWriter]): output writer, where None
              indicates that the stdout output writer should be used.
        """
        super().__init__(input_reader=input_reader, output_writer=output_writer)
        self._analysis_manager = analysis_manager.AnalysisPluginManager
        self._analysis_plugins = None
        self._analysis_plugins_output_format = None
        self._command_line_arguments = None
        self._event_filter_expression = None
        self._event_filter = None
        self._number_of_stored_analysis_reports = 0
        self._status_view_interval = 0.5
        self._storage_file_path = None
        self._worker_memory_limit = None
        self._worker_timeout = None

        self.list_analysis_plugins = False

    def _AnalyzeEvents(self, session, configuration, status_update_callback=None):
        """Analyzes events in a Plaso storage.

        Args:
          session (Session): session in which the events are analyzed.
          configuration (ProcessingConfiguration): processing configuration.
          status_update_callback (Optional[function]): callback function for status
              updates.

        Returns:
          ProcessingStatus: processing status.

        Raises:
          RuntimeError: if a non-recoverable situation is encountered.
        """
        storage_writer = storage_factory.StorageFactory.CreateStorageWriterForFile(
            self._storage_file_path
        )
        if not storage_writer:
            raise RuntimeError("Unable to create storage writer.")

        # TODO: add single process analysis engine support.
        analysis_engine = multi_analysis_engine.AnalysisMultiProcessEngine(
            worker_memory_limit=self._worker_memory_limit,
            worker_timeout=self._worker_timeout,
        )

        analysis_engine.SetStatusUpdateInterval(self._status_view_interval)

        storage_writer.Open(path=self._storage_file_path)

        processing_status = None

        session.command_line_arguments = self._command_line_arguments
        session.debug_mode = self._debug_mode

        try:
            storage_writer.AddAttributeContainer(session)

            try:
                processing_status = analysis_engine.AnalyzeEvents(
                    session,
                    storage_writer,
                    self._data_location,
                    self._analysis_plugins,
                    configuration,
                    event_filter=self._event_filter,
                    event_filter_expression=self._event_filter_expression,
                    status_update_callback=status_update_callback,
                    storage_file_path=self._storage_file_path,
                )

            finally:
                session.aborted = getattr(processing_status, "aborted", True)
                session.completion_time = int(time.time() * 1000000)
                storage_writer.UpdateAttributeContainer(session)

        finally:
            storage_writer.Close()

        return processing_status

    def _PrintAnalysisReportsDetails(self, storage_reader):
        """Prints the details of the analysis reports.

        Args:
          storage_reader (StorageReader): storage reader.
        """
        generator = storage_reader.GetAttributeContainers(
            self._CONTAINER_TYPE_ANALYSIS_REPORT
        )

        for index, analysis_report in enumerate(generator):
            if index + 1 <= self._number_of_stored_analysis_reports:
                continue

            date_time_string = None
            if analysis_report.time_compiled is not None:
                date_time = dfdatetime_posix_time.PosixTimeInMicroseconds(
                    timestamp=analysis_report.time_compiled
                )
                date_time_string = date_time.CopyToDateTimeStringISO8601()

            title = f"Analysis report: {analysis_report.plugin_name:s}"
            table_view = views.ViewsFactory.GetTableView(
                self._views_format_type, title=title
            )

            table_view.AddRow(["Date and time", date_time_string or "N/A"])
            table_view.AddRow(["Event filter", analysis_report.event_filter or "N/A"])

            if not analysis_report.analysis_counter:
                table_view.AddRow(["Text", analysis_report.text or ""])
            else:
                text = "Results"
                for key, value in sorted(analysis_report.analysis_counter.items()):
                    table_view.AddRow([text, f"{key:s}: {value:d}"])
                    text = ""

            table_view.Write(self._output_writer)
