"""Behaviour of the LoggingProcessorCallback when wired into the processing
lifecycle. Logging is observed through the standard logging framework
(pytest's ``caplog``) rather than by inspecting the callback's internals.
"""
import logging
import threading

from messagebus import Event

from messageprocessorutil import CallBackMethod, LogConfig, LoggingProcessorCallback

from testutil import BaseProcessor

LOGGER_NAME = "messageprocessor.tests.logging"


class FailingBusiness(BaseProcessor):
    def call_business(self, obj):
        raise ZeroDivisionError("division by zero")


class FailingParse(BaseProcessor):
    def parse_data(self, data=None):
        raise ValueError("bad input")


def _logging_handler(callbacks_logger, **kwargs):
    return LoggingProcessorCallback(callbacks_logger, **kwargs)


def test_logs_an_unhandled_exception_at_error_with_traceback(build_handler, message_bus, caplog):
    caplog.set_level(logging.DEBUG, logger=LOGGER_NAME)
    callback = _logging_handler(logging.getLogger(LOGGER_NAME))
    handler = build_handler(event_processors={"orders": FailingBusiness("orders")}, callbacks=[callback])

    handler.handle_event(message_bus, Event("orders", None, b""))

    errors = [r for r in caplog.records if r.levelno == logging.ERROR]
    assert len(errors) == 1
    assert errors[0].exc_info[0] is ZeroDivisionError


def test_does_not_log_ignored_exceptions(build_handler, message_bus, caplog):
    caplog.set_level(logging.DEBUG, logger=LOGGER_NAME)
    callback = _logging_handler(logging.getLogger(LOGGER_NAME), ignored_exceptions=[ZeroDivisionError])
    handler = build_handler(event_processors={"orders": FailingBusiness("orders")}, callbacks=[callback])

    handler.handle_event(message_bus, Event("orders", None, b""))

    assert not any(r.levelno >= logging.WARNING for r in caplog.records)
    assert not any(r.exc_info for r in caplog.records)


def test_logs_an_exception_at_a_configured_custom_level(build_handler, message_bus, caplog):
    caplog.set_level(logging.DEBUG, logger=LOGGER_NAME)
    callback = _logging_handler(
        logging.getLogger(LOGGER_NAME),
        log_level_exceptions={ZeroDivisionError: logging.WARNING},
    )
    handler = build_handler(event_processors={"orders": FailingBusiness("orders")}, callbacks=[callback])

    handler.handle_event(message_bus, Event("orders", None, b""))

    warnings = [r for r in caplog.records if r.levelno == logging.WARNING]
    assert len(warnings) == 1
    assert warnings[0].exc_info[0] is ZeroDivisionError
    assert not any(r.levelno >= logging.ERROR for r in caplog.records)


def test_logs_a_parse_exception_at_error(build_handler, message_bus, caplog):
    caplog.set_level(logging.DEBUG, logger=LOGGER_NAME)
    callback = _logging_handler(logging.getLogger(LOGGER_NAME))
    handler = build_handler(event_processors={"orders": FailingParse("orders")}, callbacks=[callback])

    handler.handle_event(message_bus, Event("orders", None, b""))

    errors = [r for r in caplog.records if r.levelno == logging.ERROR]
    assert len(errors) == 1
    assert errors[0].exc_info[0] is ValueError


def test_logs_each_lifecycle_stage_on_a_successful_run(build_handler, message_bus, caplog):
    caplog.set_level(logging.DEBUG, logger=LOGGER_NAME)
    callback = _logging_handler(logging.getLogger(LOGGER_NAME))
    handler = build_handler(event_processors={"orders": BaseProcessor("orders")}, callbacks=[callback])

    handler.handle_event(message_bus, Event("orders", None, b""))

    finished = [r for r in caplog.records if r.getMessage() == "Successfully processed"]
    assert len(finished) == 1
    assert finished[0].levelno == logging.INFO
    assert any(
        r.levelno == logging.DEBUG and r.getMessage().startswith("Start handling")
        for r in caplog.records
    )


def test_custom_config_overrides_the_level_for_a_stage(build_handler, message_bus, caplog):
    caplog.set_level(logging.DEBUG, logger=LOGGER_NAME)
    callback = _logging_handler(
        logging.getLogger(LOGGER_NAME),
        custom_config=[LogConfig(method=CallBackMethod.process_finished, event_name=None, log_level=logging.WARNING)],
    )
    handler = build_handler(event_processors={"orders": BaseProcessor("orders")}, callbacks=[callback])

    handler.handle_event(message_bus, Event("orders", None, b""))

    finished = [r for r in caplog.records if r.getMessage() == "Successfully processed"]
    assert len(finished) == 1
    assert finished[0].levelno == logging.WARNING


def test_tags_the_thread_with_a_descriptive_name_during_processing_and_restores_it(build_handler, message_bus):
    class ThreadNameCapturer(BaseProcessor):
        captured = None

        def call_business(self, obj):
            ThreadNameCapturer.captured = threading.current_thread().name
            return obj

    callback = _logging_handler(logging.getLogger(LOGGER_NAME))
    handler = build_handler(event_processors={"orders": ThreadNameCapturer("orders")}, callbacks=[callback])
    original_name = threading.current_thread().name

    handler.handle_event(message_bus, Event("orders", "created", b""))

    assert ThreadNameCapturer.captured.startswith("MessageProcessorThread$")
    assert threading.current_thread().name == original_name
