from __future__ import annotations

import dataclasses
import datetime
import pathlib
import uuid
from functools import cached_property
from typing import Optional, Protocol

import forcen_public_utils.loggers.log_path_utils as log_paths

KILOBYTES = 10**3
MEGABYTES = 10**6
GIGABYTES = 10**9

LOGGING_APP = "forcen_logging"


@dataclasses.dataclass(frozen=True)
class LoggerConfig:
    """Configuration options common to all loggers.

    Logs are stored at <log_root>/<app_name>/logs/<datetime>_<logger_id>/<log_file_name>

    Args:
        app_name (str): the name of the app running the logger.
        log_file_name (str): the name of the log file, including the extension.
        log_root (pathlib.Path, optional): Root directory for logging.
            Default is <user_tmp_folder>/Forcen/.
        logger_id (uuid.UUID, optional): unique ID for the logger.
            Defaults to a new uuid4().
        max_file_size (Optional[int], optional): Rotate log files once they hit this size, in bytes.
            Defaults to None.
            Rotated files are saved as <logfile_name>.<rotation_datetime>.<ext>
        max_file_age (Optional[datetime.timedelta], optional): Once log files are older than this,
            they will be deleted at end of program or on file rotation, whichever comes first.

    Properties:
        log_file_path: the full path to the log file generated by this configuration object.
            Note that the file is created the first time this property is read.
    """

    app_name: str
    log_file_name: str
    log_root: pathlib.Path = log_paths.get_forcen_tmp_root()
    logger_id: uuid.UUID = dataclasses.field(default_factory=uuid.uuid4)
    max_file_size: Optional[int] = 10_000_000
    max_file_age: Optional[datetime.timedelta] = datetime.timedelta(days=14)
    execution_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)

    def copy(self, keep_id: bool=False) -> LoggerConfig:
        """Get a copy of this config object, with a new unique id."""
        if keep_id:
            return dataclasses.replace(self)
        else:
            return dataclasses.replace(self, logger_id=uuid.uuid4())

    @cached_property
    def log_file_path(self) -> pathlib.Path:
        """Get the absolute file path to the log file.

        Returns:
            pathlib.Path: if the log file path is valid and can be resolved.
            None: if the log file path was not provided.
        """
        return (
            log_paths.get_forcen_app_log_exec_instance_path(
                user_tmp_root=self.log_root,
                app_name=self.app_name,
                execution_date=self.execution_date,
                execution_uuid=self.logger_id,
            )
            / self.log_file_name
        ).resolve()


def get_default_logger_config(
    app_name: str = LOGGING_APP, log_file_name: str = "main.log"
) -> LoggerConfig:
    return LoggerConfig(app_name=app_name, log_file_name=log_file_name)


# ============================================================================ #
# Exceptions #
# ---------- #


class LoggerError(Exception):
    pass


# ============================================================================ #
# Interfaces #
# ---------- #


class ToggleableLogging(Protocol):
    def enable_logging(self) -> None: ...

    def disable_logging(self) -> None: ...

    def is_logging_enabled(self) -> bool: ...
