# SPDX-FileCopyrightText: © 2024-2025 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
# This file is part of SPECTRE
# SPDX-License-Identifier: GPL-3.0-or-later

import os
import typing
import collections

import spectre_core.io
import spectre_core.config


def parse_log_file_name(file_name: str) -> tuple[str, str, str]:
    """Parse the file name of a log into a start time, process ID and process type."""
    base_file_name, _ = os.path.splitext(file_name)
    log_start_time, pid, process_type = base_file_name.split("_")
    return log_start_time, pid, process_type


class Log(spectre_core.io.Base):
    def __init__(self, file_path: str) -> None:
        """Interface to read log files generated by Spectre.

        :param file_path: The file path of the log.
        """
        super().__init__(file_path)
        self._start_time, self._pid, self._process_type = parse_log_file_name(
            self.file_name
        )

    @property
    def start_time(self) -> str:
        """The system time when the log was created."""
        return self._start_time

    @property
    def pid(self) -> str:
        """The ID of the process writing to the log file."""
        return self._pid

    @property
    def process_type(self) -> str:
        """Indicates the type of process."""
        return self._process_type

    def read(self) -> str:
        """Read the contents of the log file."""
        return spectre_core.io.read_file(
            self.file_path, spectre_core.io.FileFormat.TEXT
        )


class Logs:
    def __init__(
        self,
        process_type: typing.Optional[str] = None,
        logs_dir_path: typing.Optional[str] = None,
    ) -> None:
        """An interface to sort and read logs generated by Spectre.

        :param process_type: Optionally filter by process type, defaults to None
        :param logs_dir_path: Optionally override the directory containing the logs, defaults to None
        """

        self.process_type = process_type
        self._logs_dir_path = (
            logs_dir_path or spectre_core.config.paths.get_logs_dir_path()
        )

        self._log_map: dict[str, Log] = collections.OrderedDict()
        self.__update()

    def __update(self) -> None:
        """Perform a fresh search of all files according to the date and process type."""
        paths = []
        for root, _, files in os.walk(self._logs_dir_path):
            for file in files:
                paths.append(os.path.join(root, file))

        for path in paths:
            file_name = os.path.basename(path)
            _, _, process_type = parse_log_file_name(file_name)

            if self.process_type and process_type != self.process_type:
                continue

            self._log_map[file_name] = Log(path)

        self._log_map = collections.OrderedDict(sorted(self._log_map.items()))

    def __iter__(self) -> typing.Iterator[Log]:
        yield from self._log_map.values()

    def get_from_file_name(self, file_name: str) -> Log:
        """Retrieve a log based on the file name.

        :param file_name: The name of the log file (with or without extension).
        :raises FileNotFoundError: If the log file name is not found.
        :return: The log with the file name.
        """
        # auto strip the extension if present
        file_name, _ = os.path.splitext(file_name)
        try:
            return self._log_map[file_name]
        except KeyError:
            raise FileNotFoundError(f"Log not found with file name '{file_name}'")

    def get_from_pid(self, pid: str) -> Log:
        """Retrieve a log based on the process ID.

        :param pid: The process ID to search for.
        :raises FileNotFoundError: If a log file corresponding to the process ID is not found.
        :return: The log corresponding to the process ID.
        """
        for log in self:
            if log.pid == pid:
                return log
        raise FileNotFoundError(f"Log not found with pid '{pid}'")
