"""Logging manager for storing and formatting logs."""

import datetime
from threading import Lock
from typing import Callable, List, Optional

from app.constants import LogLevel, TextColor
from app.file_manager.file_manager import FileManager


class LogManager:
    """
    Stores logs in memory and optionally handles writing them to a file.
    Also provides console/file formatting and concurrency control.

    :param console_level: Minimum log level for console output.
    :type console_level: LogLevel
    :param file_level: Minimum log level for file output.
    :type file_level: LogLevel
    :param file_manager: Optional FileManager for writing logs to a file.
    :type file_manager: Optional[FileManager]
    :param console_formatter: Callable for console lines.
    :type console_formatter: Optional[Callable[
        [LogLevel, str, str, TextColor], str]]
    :param file_formatter: Callable for file lines.
    :type file_formatter: Optional[Callable[[LogLevel, str, str], str]]
    :param time_format: Timestamp format to use.
    :type time_format: str
    :param thread_safe: Whether to guard operations with a lock.
    :type thread_safe: bool
    """

    def __init__(
        self,
        console_level: LogLevel,
        file_level: LogLevel,
        file_manager: Optional[FileManager],
        console_formatter: Optional[
            Callable[[LogLevel, str, str, TextColor], str]],
        file_formatter: Optional[Callable[[LogLevel, str, str], str]],
        time_format: str,
        thread_safe: bool
    ) -> None:
        """
        Constructor that sets up the log manager.
        """
        self.console_level = console_level
        self.file_level = file_level
        self.file_manager = file_manager
        self.console_formatter = console_formatter
        self.file_formatter = file_formatter
        self.time_format = time_format
        self._thread_safe = thread_safe

        self._log_lines: List[str] = []
        self._lock = Lock() if thread_safe else None

    def log(
        self,
        level: LogLevel,
        message: str,
        color: TextColor
    ) -> Optional[str]:
        """
        Main entry for logging a single message. Checks console/file levels,
        formats lines accordingly, stores or writes them, and returns the
        final timestamp if we need to trigger a redraw.

        :param level: The message's log level.
        :type level: LogLevel
        :param message: The actual message to log.
        :type message: str
        :param color: The color for the console text.
        :type color: TextColor
        :return: A timestamp string if console output occurred, else None.
        :rtype: Optional[str]
        """
        if self._thread_safe and self._lock:
            with self._lock:
                return self._perform_log(level, message, color)
        else:
            return self._perform_log(level, message, color)

    def _perform_log(
        self,
        level: LogLevel,
        message: str,
        color: TextColor
    ) -> Optional[str]:
        """
        Internal helper that performs the actual logging logic without locks.

        :param level: The message's log level.
        :type level: LogLevel
        :param message: The message to log.
        :type message: str
        :param color: The color for the console text.
        :type color: TextColor
        :return: Timestamp if console-level logging occurred, else None.
        :rtype: Optional[str]
        """
        timestamp: Optional[str] = None

        if level.value >= self.console_level.value:
            timestamp = datetime.datetime.now().strftime(self.time_format)
            if self.console_formatter:
                line = self.console_formatter(level, message, timestamp, color)
            else:
                line = f"[{timestamp}] {level.name}: {
                    color.value}{message}{TextColor.RESET.value}"
            self._log_lines.append(line)

        if self.file_manager and level.value >= self.file_level.value:
            if not timestamp:
                timestamp = datetime.datetime.now().strftime(self.time_format)
            if self.file_formatter:
                file_line = self.file_formatter(level, message, timestamp)
            else:
                file_line = f"[{timestamp}] {level.name}: {message}"
            self.file_manager.write_line(file_line)

        return timestamp

    def log_multiline(
        self,
        level: LogLevel,
        text_block: str,
        color: TextColor
    ) -> None:
        """
        Logs multiple lines, each with the same level/timestamp.

        :param level: The level for all lines.
        :type level: LogLevel
        :param text_block: The multi-line string to log.
        :type text_block: str
        :param color: Console color for all lines.
        :type color: TextColor
        """
        lines = text_block.split("\n")
        for ln in lines:
            self.log(level, ln, color)

    def get_lines(self) -> List[str]:
        """
        Returns a copy of the in-memory console lines.

        :return: A list of all console log lines.
        :rtype: List[str]
        """
        if self._thread_safe and self._lock:
            with self._lock:
                return list(self._log_lines)
        else:
            return list(self._log_lines)

    def clear_logs(self) -> None:
        """
        Clears all in-memory console lines.
        """
        if self._thread_safe and self._lock:
            with self._lock:
                self._log_lines.clear()
        else:
            self._log_lines.clear()

    def close_file(self) -> None:
        """
        Closes the file manager if present.
        """
        if self.file_manager:
            self.file_manager.close()

    def create_child_logger(
        self,
        console_level: Optional[LogLevel] = None,
        file_level: Optional[LogLevel] = None
    ) -> "LogManager":
        """
        Demonstrates how you might create a child logger with different levels.
        The child reuses the same file manager, formatters, etc., but can have
        different levels.

        :param console_level: New console level if desired.
        :type console_level: Optional[LogLevel]
        :param file_level: New file level if desired.
        :type file_level: Optional[LogLevel]
        :return: A new LogManager that shares the same resources.
        :rtype: LogManager
        """
        new_console = console_level if console_level else self.console_level
        new_file = file_level if file_level else self.file_level
        child = LogManager(
            console_level=new_console,
            file_level=new_file,
            file_manager=self.file_manager,  # shared
            console_formatter=self.console_formatter,
            file_formatter=self.file_formatter,
            time_format=self.time_format,
            thread_safe=self._thread_safe
        )
        # Potentially share logs? or start fresh?
        # We'll start fresh for a simpler example.
        return child
