import logging
import sys

from functools import wraps
from typing import Any, Callable, TypeVar

DEFAULT_LOGGER = None


def get_logger(new_logger: bool = False):
    global DEFAULT_LOGGER
    if new_logger or not DEFAULT_LOGGER:
        log_handler = logging.StreamHandler(sys.stdout)
        log_handler.setFormatter(
            logging.Formatter("%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s")
        )
        DEFAULT_LOGGER = logging.getLogger("defaultLogger")
        DEFAULT_LOGGER.addHandler(log_handler)
        DEFAULT_LOGGER.setLevel(logging.DEBUG)
    return DEFAULT_LOGGER


F = TypeVar(
    "F", bound=Callable[..., Any]
)  # Tells type checkers that `log_it` returns a callable with the same signature as the input callable


def log_it(func: F) -> F:
    @wraps(func)
    def __call__(*args, **kwargs):
        logger = DEFAULT_LOGGER
        log_args = args
        log_kwargs = ", ".join([f"{k}={v!r}" for k, v in kwargs.items()])
        if len(args) > 0 and isinstance(args[0], object):
            if isinstance(getattr(args[0], "_logger", None), logging.Logger):
                logger = getattr(args[0], "_logger")
            if hasattr(args[0], func.__name__):  # Object is a class and `func` is a method
                # Skip `self` or `cls` argument
                log_args = ", ".join([repr(arg) for arg in args[1:]])
        if logger:
            logger.debug(  # try to print the function call as it is written in the code
                f"-- {func.__module__}.{func.__qualname__}({log_args}{', ' if log_args and log_kwargs else ''}{log_kwargs}) --"
            )
        return func(*args, **kwargs)

    return __call__  # type: ignore
