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

import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;

/**
 * Liest die Log-Verzeichnis-Angabe so früh wie möglich aus der aktiven Konfigurationsdatei
 * und setzt sie als System-Property {@code log.directory}, bevor Log4j2 zum ersten Mal
 * initialisiert wird.
 * <p>
 * Hintergrund: {@code log4j2.xml} referenziert {@code ${sys:log.directory}} für den
 * Pfad der Rolling-File-Datei. Da Log4j2 sich beim ersten {@code LogManager}-Aufruf
 * einmalig konfiguriert, muss die System-Property bereits vorher gesetzt sein. Greift
 * die Property nicht, fällt {@code log4j2.xml} auf ein nutzerschreibbares
 * Default-Verzeichnis zurück, damit auch im MSI-Betrieb (Arbeitsverzeichnis unter
 * {@code Program Files}, typischerweise nicht beschreibbar) eine Log-Datei entsteht.
 * <p>
 * <strong>Backslash-Behandlung:</strong> Wie der reguläre Konfigurationsleser werden
 * Backslashes vor dem {@link Properties}-Laden verdoppelt, damit Windows-Pfade wie
 * {@code C:\Users\...\logs} nicht durch die Escape-Interpretation von
 * {@link Properties} verstümmelt werden. Der aufgelöste Pfad wird zusätzlich auf
 * Forward-Slashes normalisiert.
 * <p>
 * <strong>Verzeichnisanlage:</strong> Das konfigurierte Log-Verzeichnis wird per
 * Best-Effort mit {@link Files#createDirectories(Path, java.nio.file.attribute.FileAttribute[])}
 * angelegt. Gelingt das nicht (z. B. fehlende Rechte oder ungültiger Pfad), bleibt die
 * System-Property ungesetzt, sodass der in {@code log4j2.xml} hinterlegte, garantiert
 * beschreibbare Fallback greift. Der Start wird niemals verhindert.
 * <p>
 * Diese Klasse vermeidet bewusst jede Logger-Nutzung (sie läuft vor der Log4j2-Init).
 * Das Ergebnis wird als {@link LogDirectorySetupResult} zurückgegeben, damit der Aufrufer
 * nach erfolgter Log4j2-Initialisierung eine sichtbare Meldung über das effektive
 * Log-Verzeichnis bzw. einen genutzten Fallback protokollieren kann.
 */
public final class EarlyLogDirectoryInitializer {

    private static final String SYSTEM_PROPERTY_KEY = "log.directory";
    private static final String CONFIG_OPTION = "--config";
    private static final Path DEFAULT_CONFIG_PATH = Paths.get("config/application.properties");
    private static final String CONFIG_PROPERTY_KEY = "log.directory";

    private EarlyLogDirectoryInitializer() {
        // utility
    }

    /**
     * Ergebnis der frühen Log-Verzeichnis-Auflösung.
     * <p>
     * Dient ausschließlich der nachgelagerten, sichtbaren Protokollierung nach der
     * Log4j2-Initialisierung. Er trifft keine weitere Entscheidung.
     *
     * @param configuredValue der aus der Konfiguration aufgelöste, normalisierte Wert von
     *                        {@code log.directory}, oder {@code null}, wenn keiner ermittelt
     *                        werden konnte
     * @param appliedDirectory das tatsächlich als System-Property gesetzte und angelegte
     *                        Verzeichnis, oder {@code null}, wenn kein konfiguriertes
     *                        Verzeichnis angewandt wurde (Fallback greift)
     * @param fallbackUsed    {@code true}, wenn ein konfiguriertes Log-Verzeichnis vorlag,
     *                        aber nicht angelegt/genutzt werden konnte und deshalb auf das
     *                        Standard-Log-Verzeichnis ausgewichen wird
     */
    public record LogDirectorySetupResult(String configuredValue, String appliedDirectory,
            boolean fallbackUsed) {

        private static LogDirectorySetupResult notConfigured() {
            return new LogDirectorySetupResult(null, null, false);
        }

        private static LogDirectorySetupResult applied(String configuredValue, String appliedDirectory) {
            return new LogDirectorySetupResult(configuredValue, appliedDirectory, false);
        }

        private static LogDirectorySetupResult fallback(String configuredValue) {
            return new LogDirectorySetupResult(configuredValue, null, true);
        }
    }

    /**
     * Versucht, aus der aktiven Konfigurationsdatei den Wert von {@code log.directory}
     * zu lesen, das Verzeichnis anzulegen und den Pfad als System-Property zu setzen.
     * <p>
     * Greift {@code --config <pfad>} auf, ansonsten {@code config/application.properties}
     * relativ zum Arbeitsverzeichnis. Ist bereits eine System-Property gesetzt, wird sie
     * respektiert. Kann kein Wert ermittelt oder das Verzeichnis nicht angelegt werden,
     * bleibt die System-Property ungesetzt; in diesem Fall greift der in {@code log4j2.xml}
     * hinterlegte Fallback.
     *
     * @param args Kommandozeilenargumente, dürfen {@code null} sein
     * @return das Ergebnis der Auflösung für die nachgelagerte, sichtbare Protokollierung;
     *         nie {@code null}
     */
    public static LogDirectorySetupResult applyFromArgs(String[] args) {
        if (isLogPropertyAlreadySet()) {
            return LogDirectorySetupResult.applied(null, System.getProperty(SYSTEM_PROPERTY_KEY));
        }

        String configuredValue = null;
        try {
            Path configPath = resolveConfigPath(args);
            if (configPath == null || !Files.isRegularFile(configPath)) {
                return LogDirectorySetupResult.notConfigured();
            }
            configuredValue = readLogDirectoryValue(configPath);
            if (configuredValue == null || configuredValue.isBlank()) {
                return LogDirectorySetupResult.notConfigured();
            }

            Path directory = Paths.get(configuredValue);
            Files.createDirectories(directory);
            String applied = directory.toString();
            System.setProperty(SYSTEM_PROPERTY_KEY, applied);
            return LogDirectorySetupResult.applied(configuredValue, applied);
        } catch (IOException | RuntimeException e) {
            // Bewusst still: kein Logger verfügbar. Bei bekanntem, aber nicht nutzbarem
            // konfiguriertem Wert wird ein Fallback signalisiert, damit der Aufrufer nach
            // der Log4j2-Init eine sichtbare Warnung ausgeben kann. Die System-Property
            // bleibt ungesetzt, sodass der log4j2.xml-Fallback greift.
            if (configuredValue != null && !configuredValue.isBlank()) {
                return LogDirectorySetupResult.fallback(configuredValue);
            }
            return LogDirectorySetupResult.notConfigured();
        }
    }

    private static boolean isLogPropertyAlreadySet() {
        String val = System.getProperty(SYSTEM_PROPERTY_KEY);
        return val != null && !val.isBlank();
    }

    /**
     * Liest den Wert von {@code log.directory} aus der Konfigurationsdatei, verdoppelt zuvor
     * Backslashes (analog zum regulären Konfigurationsleser) und normalisiert den Pfad auf
     * Forward-Slashes.
     *
     * @param configPath Pfad zur Konfigurationsdatei; darf nicht {@code null} sein
     * @return der normalisierte Wert, oder {@code null}, wenn nicht vorhanden oder leer
     * @throws IOException wenn die Datei nicht gelesen werden kann
     */
    private static String readLogDirectoryValue(Path configPath) throws IOException {
        String content = Files.readString(configPath, StandardCharsets.UTF_8);
        // Backslashes verdoppeln, sonst interpretiert Properties z. B. \U/\D/\t als Escape.
        String escapedContent = content.replace("\\", "\\\\");
        Properties properties = new Properties();
        properties.load(new StringReader(escapedContent));
        String value = properties.getProperty(CONFIG_PROPERTY_KEY);
        if (value == null || value.isBlank()) {
            return null;
        }
        return value.trim().replace('\\', '/');
    }

    private static Path resolveConfigPath(String[] args) {
        if (args != null) {
            for (int i = 0; i < args.length - 1; i++) {
                if (CONFIG_OPTION.equals(args[i])) {
                    String raw = args[i + 1];
                    if (raw != null && !raw.isBlank()) {
                        try {
                            return Paths.get(raw);
                        } catch (RuntimeException ignored) {
                            return null;
                        }
                    }
                }
            }
        }
        return DEFAULT_CONFIG_PATH;
    }
}
