"""Rads logging functions"""

from typing import Literal, Union, IO
import sys
import os
from pathlib import Path
import logging
from logging.handlers import WatchedFileHandler
from .color import red, yellow


def setup_logging(
    path: Union[Path, str, None],
    name: Union[str, None] = None,
    fmt: str = '%(asctime)s %(levelname)s %(message)s',
    datefmt: str = r'%Y-%m-%d %H:%M:%S',
    multiline: bool = True,
    loglevel: Union[int, str] = logging.DEBUG,
    print_out: Union[IO, Literal['stdout', 'stderr'], None] = None,
    print_loglevel: Union[int, str, None] = None,
    chown: Union[tuple[int, int], None] = None,
    chmod: Union[int, None] = None,
) -> logging.Logger:
    """Sets up and returns the root logger, or a named logger if ``name`` is set

    Args:
        path: file path to log to. If set to None, print_out must not be None.
        name: logger name for logging.getLogger()
        fmt: format for ``logging.Formatter``
        datefmt: date format for ``logging.Formatter``
        multiline: whether to support multiline logging
        loglevel: loglevel for logging.setLevel - will accept a constant from
            the logging module such as logging.INFO, or the string of the level
            name, e.g. 'INFO'
        print_out: set this to ``sys.stdout`` or ``sys.stderr`` to
            also print there. Also accepts 'stdout' or 'stderr' as literal str
        print_loglevel: optional separate log level for the ``print_out`` kwarg.
            If unset, it will use the ``loglevel`` kwarg.
        chown: ensure ownership of the log path
            Only valid if path is set.
        chmod: ensure perms of the log path *in octal*.
            Only valid if path is set.
    """
    if isinstance(loglevel, str):
        loglevel = getattr(logging, loglevel.upper())
        assert isinstance(loglevel, int)
    if isinstance(print_loglevel, str):
        print_loglevel = getattr(logging, print_loglevel.upper())
        assert isinstance(print_loglevel, int)
    if isinstance(print_out, str):
        if print_out.lower() == 'stdout':
            print_out = sys.stdout
        elif print_out.lower() == 'stderr':
            print_out = sys.stderr
        else:
            raise TypeError(print_out)
    if path:
        path = Path(path)
        path.touch(mode=0o644 if chmod is None else chmod, exist_ok=True)
    elif not print_out:
        raise TypeError("At least one of 'path' and/or 'print_out' must be set")
    if chmod is not None:
        if not path:
            raise TypeError("'path' must be set to use 'chmod'")
        os.chmod(path, chmod)
    if chown is not None:
        if not isinstance(chown, tuple):
            raise TypeError("'chown' must be a tuple")
        if not path:
            raise TypeError("'path' must be set to use 'chown'")
        os.chown(path, chown[0], chown[1])
    logger = logging.getLogger(name)
    if multiline:
        formatter = MultilineFormatter(fmt=fmt, datefmt=datefmt)
    else:
        formatter = logging.Formatter(fmt=fmt, datefmt=datefmt)
    if path:
        main_handler = WatchedFileHandler(path)
        main_handler.setFormatter(formatter)
        main_handler.setLevel(loglevel)
        logger.addHandler(main_handler)
    if print_out:
        print_handler = logging.StreamHandler(stream=print_out)
        print_handler.setFormatter(formatter)
        print_handler.setLevel(print_loglevel or loglevel)
        logger.addHandler(print_handler)
    levels = [loglevel]
    if print_loglevel is not None:
        levels.append(print_loglevel)
    logger.setLevel(min(levels))
    return logger


setup_logging.__module__ = 'rads'


def setup_verbosity(
    loglevel: Union[int, str] = 'DEBUG',
    color: Union[bool, None] = None,
    name: Union[str, None] = 'rads_verbosity',
):
    """Return a custom logger used to easily handle error and message printing.
        debug & info: prints to stdout
        warning: prints to stderr (in yellow, if enabled)
        error & critical: prints to stderr (in red, if enabled)

    Args:
        loglevel: filter to only print up to this level.
            This is especially useful to add --verbose/--quiet behavior
        color: set this True or False to force colors on or off. If unset,
            it will check if stderr is a TTY and enable colors if so
        name: name of the logger for logging.getLogger()

    Returns:
        Logger: the configured Logger object
    """
    if isinstance(loglevel, str):
        loglevel = getattr(logging, loglevel.upper())
        assert isinstance(loglevel, int)
    logger = logging.getLogger(name)
    stdout_handler = logging.StreamHandler(stream=sys.stdout)
    stdout_handler.setFormatter(logging.Formatter(fmt='%(message)s'))
    stdout_handler.addFilter(LevelFilter(logging.DEBUG, logging.INFO))
    if color is None:
        color = hasattr(sys.stderr, 'isatty') and sys.stdout.isatty()
    if color:
        err_fmt = logging.Formatter(fmt=red('%(message)s'))
        warn_fmt = logging.Formatter(fmt=yellow('%(message)s'))
    else:
        warn_fmt = err_fmt = logging.Formatter(fmt='%(message)s')
    warning_handler = logging.StreamHandler(stream=sys.stderr)
    warning_handler.setFormatter(warn_fmt)
    warning_handler.addFilter(LevelFilter(logging.WARNING, logging.WARNING))
    error_handler = logging.StreamHandler(stream=sys.stderr)
    error_handler.setFormatter(err_fmt)
    error_handler.addFilter(LevelFilter(logging.ERROR, logging.CRITICAL))
    logger.addHandler(stdout_handler)
    logger.addHandler(warning_handler)
    logger.addHandler(error_handler)
    logger.setLevel(loglevel)
    return logger


setup_verbosity.__module__ = 'rads'


class LevelFilter(logging.Filter):
    """Allows setting both a min and max log level via log.addFilter instead of
    log.setLevel"""

    __module__ = 'rads'

    def __init__(self, low, high):
        self._low = low
        self._high = high
        logging.Filter.__init__(self)

    def filter(self, record):
        if self._low <= record.levelno <= self._high:
            return True
        return False


class MultilineFormatter(logging.Formatter):
    """Subclass of logging.Formatter that can handle multiline strings.
    rads.setup_logging() will use this by default unless multiline=False"""

    __module__ = 'rads'

    def format(self, record: logging.LogRecord):
        save_msg = f'{record.msg}'
        output = ""
        for index, line in enumerate(save_msg.splitlines()):
            record.msg = line
            if index > 0:
                output += "\n"
            output += super().format(record)
        record.msg = save_msg
        record.message = output
        return output
