import logging
import os.path
import warnings

from .utils import format_iterable


class Handler:

    """
    Handler class for the global logging. Allows for limited user control
    """

    _levels = {
        "CRITICAL": logging.CRITICAL,
        "ERROR": logging.ERROR,
        "WARNING": logging.WARNING,
        "INFO": logging.INFO,
        "DEBUG": logging.DEBUG,
    }

    _filetype = ".yaml"
    _default_file = "remotemanager_log"
    _defaultpath = f"{_default_file}{_filetype}"

    def __init__(self):
        self._base = logging.getLogger("remotemanager")

        self._external = logging.getLogger("remotemanager.EXTERNAL")
        self._external.setLevel(10)  # external calls should always be logged

        self._path = Handler._defaultpath
        self._mode = "w"
        self._level = "WARNING"  # default to WARNING

        self._base.setLevel(Handler._levels[self._level])

        self._update_handlers()

    def refresh(self):
        """
        deletes and re-creates the log
        """
        self._delete_log(warn=False, force=True)
        self._update_handlers()

    @property
    def level(self):
        """
        Return the string format of the current logging level
        """
        return self._level

    @property
    def path(self):
        """
        Attribute determining the current log path
        """
        return self._path

    @property
    def write_mode(self):
        """
        returns the current write-mode of the logger
        """
        return self._mode

    @write_mode.setter
    def write_mode(self, mode):
        self._mode = mode
        self._update_handlers()

    @property
    def overwrite(self):
        return "w" in self._mode

    @overwrite.setter
    def overwrite(self, mode):
        if mode:
            self._mode = "w"
        else:
            self._mode = "a"
        self._update_handlers()

    @property
    def empty(self):
        return _check_log_empty(self._path)

    @level.setter
    def level(self, level):
        f"""
        Sets the logging level to `level`

        Arguments:
            level (str):
                Update the logging level. See the _levels attribute for
                valid options
        """
        level = level.upper()
        if level not in Handler._levels.keys():
            raise ValueError(
                "log level must be one of " f"{list(Handler._levels.keys())}"
            )
        self._base.setLevel(Handler._levels[level])
        self._level = level

    @path.setter
    def path(self, newpath):
        """
        set logging path to newpath, deleting old log if it is empty

        Arguments:
            newpath (str):
                new path to log at
        """
        if newpath is None:
            newpath = ""
        path, file = os.path.split(newpath)
        if file == "":
            file = Handler._defaultpath
        if path == "":
            path = "."

        newpath = os.path.join(path, file)
        if Handler._filetype not in newpath:
            path, file = os.path.split(newpath)
            newpath = os.path.join(path, f"{file}{Handler._filetype}")

        if not os.path.isdir(path):
            os.makedirs(path)

        self._delete_log(warn=True)

        self._path = newpath
        self._update_handlers()

    def _delete_log(self, warn: bool = False, force: bool = False):
        """
        Deletes the current log

        Arguments:
            warn (bool):
                warn the user if the log cannot be deleted
            force (bool):
                always delete
        """

        def delete_log(path):
            try:
                os.remove(path)
            except PermissionError:
                warnings.warn(f"could not remove log at {self._path} " f"(file open)")

        if _check_log_empty(self._path):
            delete_log(self._path)
        elif force:
            delete_log(self._path)
        elif warn:
            warnings.warn(f"could not remove log at {self._path} (not empty)")

    def _update_handlers(self):
        """
        refresh handlers attached to the logger
        """
        for handler in self._base.handlers:
            self._base.removeHandler(handler)
        file_handler = logging.FileHandler(self._path, mode=self._mode)
        formatter = logging.Formatter(
            "%(asctime)s " "%(levelname)-9s " "%(name)s.%(funcName)s: " "%(message)s",
            datefmt="%Y%m%d%H%M%S",
        )

        file_handler.setFormatter(formatter)
        self._base.addHandler(file_handler)

    # TODO(lbeal) can this be made more pythonic?

    def debug(self, msg, *args, **kwargs):
        """Direct passthrough for `debug` logging method"""
        if not isinstance(msg, str):
            msg = format_iterable(msg)
        self._external.debug(msg, *args, **kwargs)

    def runtime(self, msg, *args, **kwargs):
        """Direct passthrough for custom `runtime` logging method"""
        if not isinstance(msg, str):
            msg = format_iterable(msg)
        self._external.runtime(msg, *args, **kwargs)

    def info(self, msg, *args, **kwargs):
        """Direct passthrough for `info` logging method"""
        if not isinstance(msg, str):
            msg = format_iterable(msg)
        self._external.info(msg, *args, **kwargs)

    def important(self, msg, *args, **kwargs):
        """Direct passthrough for custom `important` logging method"""
        if not isinstance(msg, str):
            msg = format_iterable(msg)
        self._external.important(msg, *args, **kwargs)

    def warning(self, msg, *args, **kwargs):
        """Direct passthrough for `warning` logging method"""
        if not isinstance(msg, str):
            msg = format_iterable(msg)
        self._external.warning(msg, *args, **kwargs)

    def error(self, msg, *args, **kwargs):
        """Direct passthrough for `error` logging method"""
        if not isinstance(msg, str):
            msg = format_iterable(msg)
        self._external.error(msg, *args, **kwargs)

    def critical(self, msg, *args, **kwargs):
        """Direct passthrough for `critical` logging method"""
        if not isinstance(msg, str):
            msg = format_iterable(msg)
        self._external.critical(msg, *args, **kwargs)


def _check_log_empty(path):
    """
    check that the log at path `path` is clean
    """
    with open(path, "r") as o:
        log = o.read()
    if log.strip() == "":
        return True
    return False
