package vassistent.util;

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Asynchronous application logger with level filtering and file rotation support.
 */
public class Logger {

    /**
     * Log severity levels in ascending order.
     */
    public enum Level {
        DEBUG, INFO, WARN, ERROR
    }

    private static Level currentLevel = Level.DEBUG;

    private static boolean logToFile = true;
    private static String logFilePath = "logs/application.log";
    private static int maxFileSizeMB = 10;

    private static final DateTimeFormatter FORMATTER =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    private static final BlockingQueue<String> logQueue =
            new LinkedBlockingQueue<>();

    static {
        loadConfig();
        startLogWriterThread();
    }

    /**
     * Utility class constructor.
     */
    private Logger() {}

    /**
     * Loads logger configuration from {@code config/logger.properties}.
     */
    private static void loadConfig() {
        try (InputStream is = Logger.class.getClassLoader()
                .getResourceAsStream("config/logger.properties")) {

            if (is == null) return;

            Properties props = new Properties();
            props.load(is);

            currentLevel =
                    Level.valueOf(
                            props.getProperty("logger.level", "DEBUG"));

            logToFile =
                    Boolean.parseBoolean(
                            props.getProperty("logger.file.enabled", "true"));

            logFilePath =
                    props.getProperty("logger.file", "logs/application.log");

            maxFileSizeMB =
                    Integer.parseInt(
                            props.getProperty("logger.max.size.mb", "10"));

        } catch (Exception e) {
            System.err.println("Logger config error: " + e.getMessage());
        }
    }

    /**
     * Writes a debug-level log entry.
     *
     * @param source logical source component
     * @param message log message
     */
    public static void debug(String source, String message) {
        log(Level.DEBUG, source, message, null);
    }

    /**
     * Writes an info-level log entry.
     *
     * @param source logical source component
     * @param message log message
     */
    public static void info(String source, String message) {
        log(Level.INFO, source, message, null);
    }

    /**
     * Writes a warning-level log entry.
     *
     * @param source logical source component
     * @param message log message
     */
    public static void warn(String source, String message) {
        log(Level.WARN, source, message, null);
    }

    /**
     * Writes an error-level log entry without stack trace.
     *
     * @param source logical source component
     * @param message log message
     */
    public static void error(String source, String message) {
        log(Level.ERROR, source, message, null);
    }

    /**
     * Writes an error-level log entry with stack trace.
     *
     * @param source logical source component
     * @param message log message
     * @param t associated throwable
     */
    public static void error(String source, String message, Throwable t) {
        log(Level.ERROR, source, message, t);
    }

    /**
     * Enqueues a log entry for asynchronous output if the level passes filtering.
     *
     * @param level severity level
     * @param source logical source component
     * @param message log message
     * @param throwable optional throwable to append
     */
    public static void log(Level level, String source, String message, Throwable throwable) {

        if (level.ordinal() < currentLevel.ordinal()) return;

        String timestamp =
                LocalDateTime.now().format(FORMATTER);

        StringBuilder sb = new StringBuilder();

        sb.append(String.format("[%s] [%s] [%s] %s",
                timestamp, level, source, message));

        if (throwable != null) {
            StringWriter sw = new StringWriter();
            throwable.printStackTrace(new PrintWriter(sw));
            sb.append("\n").append(sw);
        }

        logQueue.offer(sb.toString());
    }

    /**
     * Starts the daemon thread that drains the log queue and writes outputs.
     */
    private static void startLogWriterThread() {

        Thread writerThread = new Thread(() -> {

            while (true) {

                try {

                    String logEntry = logQueue.take();

                    if (logToFile) {

                        rotateIfNeeded();

                        File logFile = new File(logFilePath);
                        logFile.getParentFile().mkdirs();

                        try (FileWriter fw = new FileWriter(logFile, true)) {
                            fw.write(logEntry + "\n");
                        }
                    }

                    System.out.println(logEntry);

                } catch (Exception e) {
                    System.err.println("Logger writer error: " + e.getMessage());
                }
            }

        });

        writerThread.setDaemon(true);
        writerThread.start();
    }

    /**
     * Rotates the active log file when size exceeds configured threshold.
     */
    private static void rotateIfNeeded() {

        try {

            File file = new File(logFilePath);

            if (!file.exists()) return;

            long maxBytes =
                    maxFileSizeMB * 1024L * 1024L;

            if (file.length() > maxBytes) {

                String archiveName =
                        logFilePath.replace(".log",
                                "_" + System.currentTimeMillis() + ".log");

                File archive = new File(archiveName);

                boolean renamed = file.renameTo(archive);

                if (renamed) {
                    System.out.println("Logger Rotation: " + archiveName);
                }
            }

        } catch (Exception e) {
            System.err.println("Logger rotation error: " + e.getMessage());
        }
    }

    /**
     * Performs a best-effort flush of queued messages during shutdown.
     */
    public static void shutdown() {

        System.out.println("Logger shutdown started");

        try {
            Thread.sleep(500); // Wait for queue flush

            while (!logQueue.isEmpty()) {
                String log = logQueue.poll();
                if (log != null) {
                    System.out.println(log);
                }
            }

        } catch (Exception e) {
            System.err.println("Logger shutdown error: " + e.getMessage());
        }
    }
}
