package org.slf4j.impl.test;

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.LogRecord;

import org.slf4j.impl.Slf4jLogger;
import org.slf4j.impl.Slf4jMDCAdapter;
import org.xbib.logging.ExtHandler;
import org.xbib.logging.ExtLogRecord;
import org.xbib.logging.LogContext;
import org.xbib.logging.LogContextSelector;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.slf4j.Marker;
import org.slf4j.helpers.BasicMarkerFactory;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class LoggerTest {

    private static final LogContext LOG_CONTEXT = LogContext.create();

    private static final java.util.logging.Logger ROOT = LOG_CONTEXT.getLogger("");

    private static final QueueHandler HANDLER = new QueueHandler();

    private static final LogContextSelector DEFAULT_SELECTOR = LogContext.getLogContextSelector();

    @BeforeAll
    public static void configureLogManager() {
        LogContext.setLogContextSelector(() -> LOG_CONTEXT);
        ROOT.addHandler(HANDLER);
    }

    @AfterAll
    public static void cleanup() throws Exception {
        LOG_CONTEXT.close();
        LogContext.setLogContextSelector(DEFAULT_SELECTOR);
    }

    @AfterEach
    public void clearHandler() {
        HANDLER.close();
    }

    @Test
    public void testLogger() {
        final Logger logger = LoggerFactory.getLogger(LoggerTest.class);
        assertInstanceOf(Slf4jLogger.class, logger, expectedTypeMessage(Slf4jLogger.class, logger.getClass()));

        // Ensure the logger logs something
        final String testMsg = "This is a test message";
        logger.info(testMsg);
        ExtLogRecord record = HANDLER.messages.poll();
        assertNotNull(record);
        assertEquals(testMsg, record.getMessage());
        assertNull(record.getParameters());

        // Test a formatted message
        logger.info("This is a test formatted {}", "{message}");
        record = HANDLER.messages.poll();
        assertNotNull(record);
        assertEquals("This is a test formatted {message}", record.getFormattedMessage());
        assertArrayEquals(new Object[] { "{message}" }, record.getParameters(), "Expected parameter not found.");
    }

    @Test
    public void testLoggerWithExceptions() {
        final Logger logger = LoggerFactory.getLogger(LoggerTest.class);

        final RuntimeException e = new RuntimeException("Test exception");
        final String testMsg = "This is a test message";
        logger.info(testMsg, e);
        LogRecord record = HANDLER.messages.poll();
        assertNotNull(record);
        assertEquals(testMsg, record.getMessage());
        assertEquals(e, record.getThrown(), "Cause is different from the expected cause");

        // Test format with the last parameter being the throwable which should set be set on the record
        logger.info("This is a test formatted {}", "{message}", e);
        record = HANDLER.messages.poll();
        assertNotNull(record);
        assertEquals("This is a test formatted {message}", record.getMessage());
        assertEquals(e, record.getThrown(), "Cause is different from the expected cause");
    }

    @Test
    public void testLoggerWithMarkers() {
        final Logger logger = LoggerFactory.getLogger(LoggerTest.class);
        final Marker marker = new BasicMarkerFactory().getMarker("test");

        logger.info(marker, "log message");
        LogRecord record = HANDLER.messages.poll();
        assertNotNull(record);
        // TODO: record.getMarker() must be same instance of "marker"
    }

    @Test
    public void testMDC() {
        assertSame(MDC.getMDCAdapter()
                .getClass(), Slf4jMDCAdapter.class,
                expectedTypeMessage(Slf4jMDCAdapter.class, MDC.getMDCAdapter()
                        .getClass()));
        final String key = Long.toHexString(System.currentTimeMillis());
        MDC.put(key, "value");
        assertEquals("value", MDC.get(key), "MDC value should be \"value\"");
        assertEquals("value", org.xbib.logging.MDC.get(key), "MDC value should be \"value\"");
    }

    private static Supplier<String> expectedTypeMessage(final Class<?> expected, final Class<?> found) {
        return () -> String.format("Expected type %s but found type %s", expected.getName(), found.getName());
    }

    private static class QueueHandler extends ExtHandler {
        final BlockingDeque<ExtLogRecord> messages = new LinkedBlockingDeque<>();

        @Override
        protected void doPublish(final ExtLogRecord record) {
            messages.add(record);
        }

        @Override
        public void flush() {
        }

        @Override
        public void close() throws SecurityException {
            messages.clear();
            setLevel(Level.ALL);
        }
    }
}
