import logging
import logging.handlers as handlers
import sys
from contextlib import contextmanager
from pathlib import Path

from astromodels import astromodels_config
from astromodels.utils.logging import (
    LogFilter,
    _console_formatter,
    _dev_formatter,
    _usr_formatter,
    astromodels_console_log_handler,
    astromodels_dev_log_handler,
    astromodels_usr_log_handler,
)

from astromodels.utils.valid_variable import is_valid_variable_name
from rich.console import Console
from rich.logging import RichHandler
from rich.theme import Theme
from threeML.config.config import threeML_config

# set up the console logging


def invalid_plugin_name(name: str, log: logging.Logger) -> None:

    if not is_valid_variable_name(name):

        log.error(
            f"Name {name} is not a valid name for a plugin. You must use a name which is "
            "a valid python identifier: no spaces, no operators (+,-,/,*), "
            "it cannot start with a number, no special characters"
        )

        raise AssertionError(
            f"Name {name} is not a valid name for a plugin. You must use a name which is "
            "a valid python identifier: no spaces, no operators (+,-,/,*), "
            "it cannot start with a number, no special characters"
        )


def get_path_of_log_dir() -> Path:
    """
    get the path to the logging directory
    """

    log_path: Path = Path(threeML_config.logging.path).expanduser()

    if not log_path.exists():

        log_path.mkdir(parents=True)

    return log_path


_log_file_names = ["usr.log", "dev.log"]


def get_path_of_log_file(log_file: str) -> Path:
    """
    returns the path of the log files
    """
    assert (
        log_file in _log_file_names
    ), f"{log_file} is not one of {_log_file_names}"

    return get_path_of_log_dir() / log_file


# now create the developer handler that rotates every day and keeps
# 10 days worth of backup
threeML_dev_log_handler = handlers.TimedRotatingFileHandler(
    get_path_of_log_file("dev.log"), when="D", interval=1, backupCount=10
)


# lots of info written out

threeML_dev_log_handler.setFormatter(_dev_formatter)
threeML_dev_log_handler.setLevel(logging.DEBUG)


# now set up the usr log which will save the info

threeML_usr_log_handler = handlers.TimedRotatingFileHandler(
    get_path_of_log_file("usr.log"), when="D", interval=1, backupCount=10
)

threeML_usr_log_handler.setLevel(logging.INFO)

# lots of info written out

threeML_usr_log_handler.setFormatter(_usr_formatter)

# now set up the console logger
_theme = {}

# Banner
_theme["h1"] = "deep_sky_blue3"
_theme["status.spinner"] = "cyan2"
_theme["status.text"] = "deep_sky_blue4"
_theme["repr.filename"] = "blue"
_theme["repr.number"] = "white"
_theme["repr.path"] = "grey37"
_theme["repr.str"] = "grey37"
_theme["repr.tag_name"] = "white"
_theme["repr.url"] = "not bold not italic underline grey84"
_theme["log.time"] = "green1"
_theme["log.message"] = f"{astromodels_config.logging.message_style}"
_theme["logging.level.debug"] = f"{astromodels_config.logging.debug_style}"
_theme["logging.level.error"] = f"{astromodels_config.logging.error_style}"
_theme["logging.level.info"] = f"{astromodels_config.logging.info_style}"
_theme["logging.level.warning"] = f"{astromodels_config.logging.warn_style}"


# mytheme = Theme().read(_get_data_file_path("log_theme.ini"))
mytheme = Theme(_theme)

console = Console(theme=mytheme)


threeML_console_log_handler = RichHandler(
    level="INFO", rich_tracebacks=True, markup=True, console=console
)
threeML_console_log_handler.setFormatter(_console_formatter)
threeML_console_log_handler.setLevel(threeML_config.logging.level)


astromodels_console_log_handler.setLevel(threeML_config.logging.level)


warning_filter = LogFilter(logging.WARNING)


####
# These control the verbosity of 3ML
####


class LoggingState(object):
    def __init__(
        self,
        threeML_usr_log_handler,
        threeML_console_log_handler,
        astromodels_usr_log_handler,
        astromodels_console_log_handler,
    ):
        """
        A container to store the stat of the logs
        """

        # attach the log handlers

        self.threeML_usr_log_handler = threeML_usr_log_handler
        self.threeML_console_log_handler = threeML_console_log_handler

        self.astromodels_usr_log_handler = astromodels_usr_log_handler
        self.astromodels_console_log_handler = astromodels_console_log_handler

        # store their current states

        self.threeML_usr_log_handler_state = threeML_usr_log_handler.level
        self.threeML_console_log_handler_state = (
            threeML_console_log_handler.level
        )

        self.astromodels_usr_log_handler_state = (
            astromodels_usr_log_handler.level
        )
        self.astromodels_console_log_handler_state = (
            astromodels_console_log_handler.level
        )

    def _store_state(self):

        self.threeML_usr_log_handler_state = threeML_usr_log_handler.level
        self.threeML_console_log_handler_state = (
            threeML_console_log_handler.level
        )

        self.astromodels_usr_log_handler_state = (
            astromodels_usr_log_handler.level
        )
        self.astromodels_console_log_handler_state = (
            astromodels_console_log_handler.level
        )

    def restore_last_state(self):

        self.threeML_usr_log_handler.setLevel(
            self.threeML_usr_log_handler_state
        )
        self.threeML_console_log_handler.setLevel(
            self.threeML_console_log_handler_state
        )

        self.astromodels_usr_log_handler.setLevel(
            self.astromodels_usr_log_handler_state
        )
        self.astromodels_console_log_handler.setLevel(
            self.astromodels_console_log_handler_state
        )

    def silence_logs(self):

        # store the state
        self._store_state()

        # silence the logs

        self.threeML_usr_log_handler.setLevel(logging.CRITICAL)
        self.threeML_console_log_handler.setLevel(logging.CRITICAL)

        self.astromodels_usr_log_handler.setLevel(logging.CRITICAL)
        self.astromodels_console_log_handler.setLevel(logging.CRITICAL)

    def loud_logs(self):

        # store the state
        self._store_state()

        # silence the logs

        self.threeML_usr_log_handler.setLevel(logging.INFO)
        self.threeML_console_log_handler.setLevel(logging.INFO)

        self.astromodels_usr_log_handler.setLevel(logging.INFO)
        self.astromodels_console_log_handler.setLevel(logging.INFO)

    def debug_logs(self):

        # store the state
        self._store_state()

        # silence the logs
        self.threeML_console_log_handler.setLevel(logging.DEBUG)

        self.astromodels_console_log_handler.setLevel(logging.DEBUG)


_log_state = LoggingState(
    threeML_usr_log_handler,
    threeML_console_log_handler,
    astromodels_usr_log_handler,
    astromodels_console_log_handler,
)


def silence_progress_bars():
    """
    Turn off the progress bars
    """

    threeML_config["interface"]["progress_bars"] = "off"


def activate_progress_bars():
    """
    Turn on the progress bars
    """
    threeML_config["interface"]["progress_bars"] = "on"


def toggle_progress_bars():
    """
    toggle the state of the progress bars
    """
    state = threeML_config["interface"]["progress_bars"]

    threeML_config["interface"]["progress_bars"] = not state


def silence_warnings():
    """
    supress warning messages in console and file usr logs
    """

    threeML_usr_log_handler.addFilter(warning_filter)
    threeML_console_log_handler.addFilter(warning_filter)

    astromodels_usr_log_handler.addFilter(warning_filter)
    astromodels_console_log_handler.addFilter(warning_filter)


def activate_warnings():
    """
    supress warning messages in console and file usr logs
    """

    threeML_usr_log_handler.removeFilter(warning_filter)
    threeML_console_log_handler.removeFilter(warning_filter)

    astromodels_usr_log_handler.removeFilter(warning_filter)
    astromodels_console_log_handler.removeFilter(warning_filter)


def update_logging_level(level):
    """
    update the logging level to the console
    """
    threeML_console_log_handler.setLevel(level)

    astromodels_console_log_handler.setLevel(level)


def silence_logs():
    """
    Turn off all logging
    """

    # handle dev logs independently
    threeML_dev_log_handler.setLevel(logging.CRITICAL)
    astromodels_dev_log_handler.setLevel(logging.CRITICAL)

    _log_state.silence_logs()


def quiet_mode():
    """
    turn off all logging and progress bars
    """

    silence_progress_bars()

    # save state and silence
    silence_logs()


def loud_mode():
    """
    turn on all progress bars and logging
    """

    activate_progress_bars()

    # save state and make loud
    _log_state.loud_logs()


def activate_logs():
    """
    re-activate silenced logs
    """

    # handle dev logs independently
    threeML_dev_log_handler.setLevel(logging.DEBUG)
    astromodels_dev_log_handler.setLevel(logging.DEBUG)

    _log_state.restore_last_state()


def debug_mode():
    """
    activate debug in the console
    """

    # store state and switch console to debug
    _log_state.debug_logs()


@contextmanager
def silence_console_log(and_progress_bars=True):
    """
    temporarily silence the console and progress bars
    """
    current_console_logging_level = threeML_console_log_handler.level
    current_usr_logging_level = threeML_usr_log_handler.level

    threeML_console_log_handler.setLevel(logging.CRITICAL)
    threeML_usr_log_handler.setLevel(logging.CRITICAL)

    if and_progress_bars:
        progress_state = threeML_config.interface.progress_bars

        threeML_config.interface.progress_bars = "off"

    try:
        yield

    finally:

        threeML_console_log_handler.setLevel(current_console_logging_level)
        threeML_usr_log_handler.setLevel(current_usr_logging_level)

        if and_progress_bars:

            threeML_config.interface.progress_bars = progress_state


def setup_logger(name: str) -> logging.Logger:

    # A logger with name name will be created
    # and then add it to the print stream
    log = logging.getLogger(name)

    # this must be set to allow debug messages through
    log.setLevel(logging.DEBUG)

    # add the handlers

    if threeML_config["logging"]["developer"]:
        log.addHandler(threeML_dev_log_handler)

    else:

        # if we do not want to log developer
        # for 3ML, then lets not for astromodels

        astromodels_dev_log_handler.setLevel(logging.CRITICAL)

    if threeML_config["logging"]["console"]:

        log.addHandler(threeML_console_log_handler)

    if threeML_config["logging"]["usr"]:
        log.addHandler(threeML_usr_log_handler)

    # we do not want to duplicate teh messages in the parents
    log.propagate = False

    return log
