package de.gecheckt.pdf.umbenenner.bootstrap.adapter;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Property;

/**
 * Test utility that captures Log4j2 log events during test execution.
 * <p>
 * Registers a list appender on the root {@link org.apache.logging.log4j.core.config.LoggerConfig}
 * via the Log4j2 {@link Configuration} API so that events from all descendant loggers propagate
 * to the appender correctly. Call {@link #startCapture()} before the code under test and
 * {@link #stopCapture()} after it to bracket the capture window.
 * <p>
 * Usage:
 * <pre>
 *   TestLogCapture capture = new TestLogCapture();
 *   capture.startCapture();
 *
 *   // Code under test
 *   logger.debug("message", arg);
 *
 *   capture.stopCapture();
 *   assertTrue(capture.containsMessage("message"));
 * </pre>
 */
class TestLogCapture {

    private final ListAppender listAppender;

    /**
     * Creates a new TestLogCapture with a uniquely named internal appender.
     */
    TestLogCapture() {
        // Unique name avoids conflicts when multiple instances are created in one test run
        this.listAppender = new ListAppender("TestListAppender-" + UUID.randomUUID());
    }

    /**
     * Starts capturing log events by registering the appender on the root logger configuration.
     * <p>
     * Uses {@link Configuration#getRootLogger()} and {@link LoggerContext#updateLoggers()} so
     * that events from all descendant loggers propagate through the configuration hierarchy to
     * the appender.
     */
    void startCapture() {
        LoggerContext context = (LoggerContext) LogManager.getContext(false);
        Configuration config = context.getConfiguration();
        listAppender.start();
        config.addAppender(listAppender);
        config.getRootLogger().addAppender(listAppender, Level.ALL, null);
        context.updateLoggers();
    }

    /**
     * Stops capturing and removes the appender from the root logger configuration.
     */
    void stopCapture() {
        LoggerContext context = (LoggerContext) LogManager.getContext(false);
        Configuration config = context.getConfiguration();
        config.getRootLogger().removeAppender(listAppender.getName());
        context.updateLoggers();
        if (listAppender.isStarted()) {
            listAppender.stop();
        }
    }

    /**
     * Checks if any captured log message contains the given substring.
     *
     * @param substring the substring to search for
     * @return true if any captured message contains the substring, false otherwise
     */
    boolean containsMessage(String substring) {
        Objects.requireNonNull(substring, "substring must not be null");
        return listAppender.getEvents()
                .stream()
                .anyMatch(event -> event.getMessage().getFormattedMessage().contains(substring));
    }

    /**
     * Returns all captured log messages as a list of formatted strings.
     *
     * @return list of formatted log messages
     */
    List<String> getMessages() {
        return listAppender.getEvents()
                .stream()
                .map(event -> event.getMessage().getFormattedMessage())
                .toList();
    }

    /**
     * Clears all captured log events.
     */
    void clear() {
        listAppender.clear();
    }

    /**
     * Internal appender that collects immutable copies of log events.
     */
    private static class ListAppender extends AbstractAppender {

        private final List<LogEvent> events = new ArrayList<>();

        ListAppender(String name) {
            super(name, null, null, false, Property.EMPTY_ARRAY);
        }

        @Override
        public void append(LogEvent event) {
            // Store an immutable copy to prevent issues with Log4j2 event object reuse
            events.add(event.toImmutable());
        }

        @Override
        public boolean ignoreExceptions() {
            return false;
        }

        List<LogEvent> getEvents() {
            return new ArrayList<>(events);
        }

        void clear() {
            events.clear();
        }
    }
}
