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

import java.nio.file.Files;
import java.nio.file.Path;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;

/**
 * Stellt das Log4j2-Log-Verzeichnis zur Laufzeit auf das in der geladenen Konfiguration
 * hinterlegte {@code log.directory} um.
 * <p>
 * Hintergrund: Im GUI-Modus wird die Anwendung typischerweise <em>ohne</em> {@code --config}
 * gestartet. {@link EarlyLogDirectoryInitializer} kann die Property {@code log.directory}
 * dann nicht aus der später in der GUI geladenen Konfigurationsdatei ableiten, sodass Log4j2
 * auf sein Standard-Log-Verzeichnis zurückfällt. Sobald die GUI eine Konfiguration lädt
 * (Auto-Reload beim Start oder „Öffnen"), stellt diese Klasse das Log-Verzeichnis
 * nachträglich auf den konfigurierten Wert um, indem sie die System-Property setzt und
 * den laufenden {@link LoggerContext} neu konfiguriert (Log4j2 löst {@code ${sys:log.directory}}
 * dabei erneut auf).
 * <p>
 * Die Umstellung ist <strong>Best-Effort</strong>: Fehler (nicht anlegbares Verzeichnis,
 * Reconfigure-Fehler) werden protokolliert und geschluckt; das bisherige Log-Verzeichnis
 * bleibt in diesem Fall aktiv. Der Programmablauf wird nie unterbrochen.
 * <p>
 * Diese Klasse betrifft ausschließlich den GUI-Pfad. Der headless Betrieb erhält das
 * Log-Verzeichnis unverändert früh über {@link EarlyLogDirectoryInitializer}.
 */
public class RuntimeLogDirectoryReconfigurer {

    private static final String SYSTEM_PROPERTY_KEY = "log.directory";
    private static final Logger LOG = LogManager.getLogger(RuntimeLogDirectoryReconfigurer.class);

    /**
     * Erstellt einen neuen Reconfigurer.
     */
    public RuntimeLogDirectoryReconfigurer() {
        // zustandslos
    }

    /**
     * Ergebnis einer Umstellungsanfrage.
     *
     * @param changed          {@code true}, wenn das Log-Verzeichnis tatsächlich umgestellt wurde
     * @param appliedDirectory das nun aktive Log-Verzeichnis, oder {@code null}, wenn nichts geändert wurde
     */
    public record LogDirectoryChange(boolean changed, String appliedDirectory) {

        private static LogDirectoryChange unchanged() {
            return new LogDirectoryChange(false, null);
        }

        private static LogDirectoryChange changedTo(String appliedDirectory) {
            return new LogDirectoryChange(true, appliedDirectory);
        }
    }

    /**
     * Stellt das Log-Verzeichnis auf {@code logDirectory} um, sofern nötig und möglich.
     * <p>
     * Ist {@code logDirectory} {@code null} oder leer, oder entspricht es bereits dem aktiven
     * Wert, geschieht nichts. Andernfalls wird das Verzeichnis angelegt (Best-Effort), die
     * System-Property {@code log.directory} gesetzt und Log4j2 neu konfiguriert.
     *
     * @param logDirectory das konfigurierte Log-Verzeichnis; darf {@code null} sein
     * @return das Ergebnis der Umstellung; nie {@code null}
     */
    public LogDirectoryChange apply(Path logDirectory) {
        if (logDirectory == null || logDirectory.toString().isBlank()) {
            return LogDirectoryChange.unchanged();
        }

        String target;
        try {
            target = logDirectory.toAbsolutePath().toString();
        } catch (RuntimeException e) {
            LOG.warn("Konfiguriertes Log-Verzeichnis '{}' konnte nicht aufgelöst werden: {} – "
                    + "bisheriges Log-Verzeichnis bleibt aktiv.", logDirectory, e.getMessage());
            return LogDirectoryChange.unchanged();
        }

        if (target.equals(System.getProperty(SYSTEM_PROPERTY_KEY))) {
            return LogDirectoryChange.unchanged();
        }

        try {
            Files.createDirectories(logDirectory.toAbsolutePath());
        } catch (Exception e) {
            LOG.warn("Konfiguriertes Log-Verzeichnis '{}' konnte nicht angelegt werden: {} – "
                    + "bisheriges Log-Verzeichnis bleibt aktiv.", target, e.getMessage());
            return LogDirectoryChange.unchanged();
        }

        System.setProperty(SYSTEM_PROPERTY_KEY, target);
        try {
            reconfigure();
        } catch (RuntimeException e) {
            LOG.warn("Log4j2 konnte nicht auf Log-Verzeichnis '{}' umgestellt werden: {}",
                    target, e.getMessage());
            return LogDirectoryChange.unchanged();
        }

        LOG.info("Log-Verzeichnis zur Laufzeit auf das konfigurierte Verzeichnis umgestellt: {}", target);
        return LogDirectoryChange.changedTo(target);
    }

    /**
     * Konfiguriert den laufenden Log4j2-{@link LoggerContext} neu, sodass
     * {@code ${sys:log.directory}} erneut aufgelöst wird.
     * <p>
     * Als eigene Methode ausgelegt, damit Tests die Log4j2-Neukonfiguration überschreiben können.
     */
    protected void reconfigure() {
        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
        ctx.reconfigure();
    }
}
