# SPDX-License-Identifier: MIT OR Apache-2.0
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the MIT License.  See the LICENSE file in the root of this
# repository for complete details.

"""
Make sure our configuration examples actually pass the type checker.
"""

from __future__ import annotations

import logging
import logging.config

from typing import Any, Callable

import structlog

from structlog.processors import CallsiteParameter
from structlog.typing import FilteringBoundLogger


bl = structlog.get_logger()
bl.msg("hello", whom="world", x=42, y={})

bls: structlog.stdlib.BoundLogger = structlog.get_logger()
bls.info("hello", whom="world", x=42, y={})


def bytes_dumps(
    __obj: Any,
    /,
    default: Callable[[Any], Any] | None = None,
    option: int | None = None,
) -> bytes:
    """
    Test with orjson's signature taken from
    <https://github.com/ijl/orjson/blob/master/orjson.pyi>.
    """
    return b"{}"


structlog.configure(
    processors=[structlog.processors.JSONRenderer(serializer=bytes_dumps)]
)


structlog.configure(
    processors=[
        structlog.stdlib.render_to_log_args_and_kwargs,
        structlog.stdlib.filter_by_level,
        structlog.stdlib.add_logger_name,
        structlog.stdlib.add_log_level,
        structlog.stdlib.PositionalArgumentsFormatter(),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.UnicodeDecoder(),
        structlog.stdlib.render_to_log_kwargs,
    ],
    context_class=dict,
    logger_factory=structlog.stdlib.LoggerFactory(),
    wrapper_class=structlog.stdlib.BoundLogger,
    cache_logger_on_first_use=True,
)

structlog.configure(
    processors=[
        structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
    ],
    logger_factory=structlog.stdlib.LoggerFactory(),
)

formatter = structlog.stdlib.ProcessorFormatter(
    processor=structlog.dev.ConsoleRenderer(),
)

formatter = structlog.stdlib.ProcessorFormatter(
    processors=[
        structlog.processors.CallsiteParameterAdder(),
        structlog.processors.CallsiteParameterAdder(
            {CallsiteParameter.FILENAME}, ["threading"]
        ),
        structlog.processors.CallsiteParameterAdder(
            {CallsiteParameter.LINENO}, additional_ignores=["threading"]
        ),
        structlog.processors.CallsiteParameterAdder(
            parameters={CallsiteParameter.FUNC_NAME},
            additional_ignores=["threading"],
        ),
        structlog.processors.CallsiteParameterAdder(
            [
                CallsiteParameter.FILENAME,
                CallsiteParameter.FUNC_NAME,
                CallsiteParameter.LINENO,
            ]
        ),
        structlog.processors.CallsiteParameterAdder(
            parameters=[
                CallsiteParameter.FILENAME,
                CallsiteParameter.FUNC_NAME,
                CallsiteParameter.LINENO,
            ]
        ),
        structlog.processors.CallsiteParameterAdder(
            parameters=[
                CallsiteParameter.FILENAME,
                CallsiteParameter.FUNC_NAME,
                CallsiteParameter.LINENO,
            ]
        ),
    ],
)

handler = logging.StreamHandler()
handler.setFormatter(formatter)
root_logger = logging.getLogger()
root_logger.addHandler(handler)
root_logger.setLevel(logging.INFO)


timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S")
shared_processors: list[structlog.typing.Processor] = [
    structlog.stdlib.add_log_level,
    timestamper,
]

structlog.configure(
    processors=[
        *shared_processors,
        structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
    ],
    logger_factory=structlog.stdlib.LoggerFactory(),
    cache_logger_on_first_use=True,
)

formatter = structlog.stdlib.ProcessorFormatter(
    processor=structlog.dev.ConsoleRenderer(),
    foreign_pre_chain=[
        structlog.stdlib.ExtraAdder(),
        structlog.stdlib.ExtraAdder(allow=None),
        structlog.stdlib.ExtraAdder(None),
        structlog.stdlib.ExtraAdder(allow=["k1", "k2"]),
        structlog.stdlib.ExtraAdder({"k1", "k2"}),
        *shared_processors,
    ],
)


timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S")
pre_chain = [
    # Add the log level and a timestamp to the event_dict if the log entry
    # is not from structlog.
    structlog.stdlib.add_log_level,
    timestamper,
]

logging.config.dictConfig(
    {
        "version": 1,
        "disable_existing_loggers": False,
        "formatters": {
            "plain": {
                "()": structlog.stdlib.ProcessorFormatter,
                "processor": structlog.dev.ConsoleRenderer(colors=False),
                "foreign_pre_chain": pre_chain,
            },
            "colored": {
                "()": structlog.stdlib.ProcessorFormatter,
                "processor": structlog.dev.ConsoleRenderer(colors=True),
                "foreign_pre_chain": pre_chain,
            },
        },
        "handlers": {
            "default": {
                "level": "DEBUG",
                "class": "logging.StreamHandler",
                "formatter": "colored",
            },
            "file": {
                "level": "DEBUG",
                "class": "logging.handlers.WatchedFileHandler",
                "filename": "test.log",
                "formatter": "plain",
            },
        },
        "loggers": {
            "": {
                "handlers": ["default", "file"],
                "level": "DEBUG",
                "propagate": True,
            },
        },
    }
)
structlog.configure(
    processors=[
        structlog.stdlib.add_log_level,
        structlog.stdlib.PositionalArgumentsFormatter(),
        timestamper,
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
    ],
    context_class=dict,
    logger_factory=structlog.stdlib.LoggerFactory(),
    wrapper_class=structlog.stdlib.BoundLogger,
    cache_logger_on_first_use=True,
)


structlog.configure(
    processors=[
        structlog.stdlib.filter_by_level,
        structlog.stdlib.add_logger_name,
        structlog.stdlib.add_log_level,
        structlog.stdlib.PositionalArgumentsFormatter(),
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.UnicodeDecoder(),
        structlog.processors.JSONRenderer(),
    ],
    context_class=dict,
    logger_factory=structlog.stdlib.LoggerFactory(),
    wrapper_class=structlog.stdlib.BoundLogger,
    cache_logger_on_first_use=True,
)

structlog.configure(
    processors=[
        structlog.stdlib.filter_by_level,
        structlog.stdlib.add_logger_name,
        structlog.stdlib.add_log_level,
        structlog.stdlib.PositionalArgumentsFormatter(),
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.UnicodeDecoder(),
        structlog.processors.LogfmtRenderer(),
    ],
    context_class=dict,
    logger_factory=structlog.stdlib.LoggerFactory(),
    wrapper_class=structlog.stdlib.BoundLogger,
    cache_logger_on_first_use=True,
)

structlog.configure(
    processors=[
        structlog.stdlib.filter_by_level,
        structlog.stdlib.add_logger_name,
        structlog.stdlib.add_log_level,
        structlog.stdlib.PositionalArgumentsFormatter(),
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.UnicodeDecoder(),
        structlog.processors.JSONRenderer(),
    ],
    context_class=dict,
    logger_factory=structlog.stdlib.LoggerFactory(),
    wrapper_class=structlog.stdlib.AsyncBoundLogger,
    cache_logger_on_first_use=True,
)

# Regression test for
# https://github.com/wemake-services/wemake-django-template/
structlog.configure(
    processors=[
        structlog.stdlib.filter_by_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.stdlib.add_logger_name,
        structlog.stdlib.add_log_level,
        structlog.stdlib.PositionalArgumentsFormatter(),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.UnicodeDecoder(),
        structlog.processors.ExceptionPrettyPrinter(),
        structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
    ],
    context_class=structlog.threadlocal.wrap_dict(dict),
    logger_factory=structlog.stdlib.LoggerFactory(),
    wrapper_class=structlog.stdlib.BoundLogger,
    cache_logger_on_first_use=True,
)

with structlog.threadlocal.bound_threadlocal(x=42):
    pass


def typecheck_filtering_return() -> None:
    fblogger: FilteringBoundLogger = structlog.get_logger(__name__)
    fblog = fblogger.bind(key1="value1", key2="value2", key3="value3")
    fblog.info("values bound")
    fblog = fblog.unbind("key1")
    fblog.debug("value unbound")
    fblog = fblog.try_unbind("bad_key")
    fblog.warn("no value unbound because key not defined")
    fblog = fblog.new(new="value")
    fblog.info("this is a whole new logger")
    fblog.log(logging.CRITICAL, "this is synchronously CRITICAL")


async def typecheck_filtering_return_async() -> None:
    fblogger: FilteringBoundLogger = structlog.get_logger(__name__)
    await fblogger.adebug("async debug")
    await fblogger.ainfo("async info")
    await fblogger.awarning("async warning")
    await fblogger.awarn("async warn")
    await fblogger.aerror("async error")
    await fblogger.afatal("fatal error")
    await fblogger.aexception("async exception")
    await fblogger.acritical("async critical")
    await fblogger.amsg("async msg")
    await fblogger.alog(logging.CRITICAL, "async log")


async def typecheck_stdlib_async() -> None:
    logger: structlog.stdlib.BoundLogger = structlog.get_logger(__name__)
    await logger.adebug("async debug")
    await logger.ainfo("async info")
    await logger.awarning("async warning")
    await logger.aerror("async error")
    await logger.afatal("fatal error")
    await logger.aexception("async exception")
    await logger.acritical("async critical")
    await logger.alog(logging.CRITICAL, "async log")


def typecheck_bound_logger_return() -> None:
    blogger: structlog.BoundLogger = structlog.get_logger(__name__)
    blog = blogger.bind(key1="value1", key2="value2", key3="value3")
    blog = blog.unbind("key1")
    blog = blog.try_unbind("bad_key")
    blog = blog.new(new="value")


# Structured tracebacks and ExceptionRenderer with ExceptionDictTransformer
struct_tb: structlog.tracebacks.Trace = structlog.tracebacks.extract(
    ValueError, ValueError("onoes"), None
)
try:
    raise ValueError("onoes")
except ValueError as e:
    struct_tb = structlog.tracebacks.extract(type(e), e, e.__traceback__)
structlog.configure(
    processors=[
        structlog.processors.ExceptionRenderer(
            structlog.tracebacks.ExceptionDictTransformer()
        ),
        structlog.processors.JSONRenderer(),
    ]
)

fbl: FilteringBoundLogger = structlog.get_logger()
fbl.info("Hello %s! The answer is %d.", "World", 42, x=1)


# Introspection
level: int = fbl.get_effective_level()
is_active: bool = fbl.is_enabled_for(logging.INFO)
is_active = fbl.is_enabled_for(20)


# contextvars


@structlog.contextvars.bound_contextvars(x=42)
def f() -> None:
    with structlog.contextvars.bound_contextvars(y=23):
        pass


# ConsoleRenderer properties

cr = structlog.dev.ConsoleRenderer.get_active()

cr.exception_formatter
cr.exception_formatter = structlog.dev.plain_traceback
cr.exception_formatter = structlog.dev.better_traceback

cr.columns
cr.columns = [
    structlog.dev.Column(
        "", structlog.dev.KeyValueColumnFormatter("", "", "", repr, 0)
    )
]

cr.colors
cr.colors = False

cr.force_colors
cr.force_colors = False

cr.level_styles
cr.level_styles = {"info": "foo"}

cr.sort_keys
cr.sort_keys = True

cr.pad_level
cr.pad_level = True

cr.pad_event_to
cr.pad_event_to = 42

cr.timestamp_key
cr.timestamp_key = "ts"

cr.event_key
cr.event_key = "le event"

cr.repr_native_str
cr.repr_native_str = True
