from functools import partial
from threading import local

import util
from schema import GrofersRequestLogSchemaAdapter

_local = local()

_logger_factory_cls = util.StructlogFactoryImpl

_schema_builder = None


def configure(schema_builder=GrofersRequestLogSchemaAdapter, **cfg):
    """
    One time setup for the lifetime of an application.
    :param schema_builder: dictifiable object
    :param cfg: kwargs that are accepted by logging.basicConfig
    """
    global _schema_builder
    util.configure(**cfg)

    if schema_builder:
        _schema_builder = schema_builder


def new_logger(**kw):
    """
    Setup once every session or request cycle.
    :param kw: kwargs bound to logger for scope of session.
    """
    global _local
    _local.logger = _logger_factory_cls()
    bind(**kw)


def get_trace_id():
    if not hasattr(_local, 'trace_id'):
        return None
    return _local.trace_id


def _set_trace_id(trace_id):
    global _local
    _local.trace_id = trace_id


def bind(request=None, **kwargs):
    global _local
    global _schema_builder

    if not hasattr(_local, 'logger'):
        raise RuntimeError("bind_logger() invoked before new_logger() invocation")

    schema = kwargs or {}
    if _schema_builder:
        schema_builder = _schema_builder(request, **schema)
        schema_dict = schema_builder.as_dict()
        _set_trace_id(schema_dict.get('gr_trace_id'))
        schema.update(schema_dict)

    _local.logger.bind(**schema)


def _get_logger(*args, **kwargs):
    global _local
    if not hasattr(_local, 'logger'):
        _local.logger = _logger_factory_cls(*args, **kwargs)

    kwargs = kwargs or {}
    return _local.logger.get_logger(*args, **kwargs)


def _log_supplier(*args, **kwargs):
    """
    Acts as a generator of the `logger` object when
    requested. This does the necessary *magic* to fetch the
    available request context.
    @param kwargs: initial values for the structlog
    @return:
    """
    return _get_logger(*args, **kwargs)


def get_logger(*args, **kwargs):
    """
    Returns a proxy to the logger object. This will ensure that
    even if the logger has been declared at the module level, it will
    still have access to the request bound context, if present.
    This will ensure that the common pattern like::

        from glogger import get_logger

        log = get_logger()

        class SomeController:
            def some_method():
                log.info('Some method of some controllers invoked')
                # the log line published above will have any
                # request bound context

    continue to work.
    @param kwargs: any initial values needed to be passed to `structlog`
    @return: proxy to the actual logger object
    """
    return util.Proxy(partial(_log_supplier, *args, **kwargs))


# CamelCase alias for `glogger.get_logger`
getLogger = get_logger
new_bounded_logger = new_logger
