package com.airlock.iam.application.webserver.infrastructure;

import com.airlock.iam.application.parameters.domain.model.LogFormat;
import com.airlock.iam.application.parameters.domain.model.WebServerLogParameters;
import com.airlock.iam.application.webserver.infrastructure.formatter.WebserverLogStructuredFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;
import org.apache.juli.AsyncFileHandler;

public final class TomcatJuliConfigurator {
   public static void configureJuli(WebServerLogParameters logParameters) {
      LogManager logManager = LogManager.getLogManager();
      logManager.reset();
      Logger rootLogger = logManager.getLogger("");
      rootLogger.setLevel(getRootLoggerLevel(logParameters));
      if (webserverLogEnabled(logParameters)) {
         setupSimpleFormatter(logParameters);
         if (logParameters.consoleOutputEnabled()) {
            rootLogger.addHandler(newConsoleHandler(logParameters));
         }

         if (logParameters.simpleFileOutputEnabled()) {
            rootLogger.addHandler(newSimpleFileHandler(logParameters));
         }

         if (logParameters.structuredFileOutputEnabled()) {
            rootLogger.addHandler(newStructuredFileHandler(logParameters));
         }
      }
   }

   private static boolean fileLoggerEnabled(WebServerLogParameters logParameters) {
      return logParameters.simpleFileOutputEnabled() || logParameters.structuredFileOutputEnabled();
   }

   private static boolean webserverLogEnabled(WebServerLogParameters logParameters) {
      return logParameters.consoleOutputEnabled() || fileLoggerEnabled(logParameters);
   }

   private static Level getRootLoggerLevel(WebServerLogParameters logParameters) {
      Level rootLevel = Level.OFF;

      for (Level level : getHandlerLevels(logParameters)) {
         if (level.intValue() < rootLevel.intValue()) {
            rootLevel = level;
         }
      }

      return rootLevel;
   }

   private static List<Level> getHandlerLevels(WebServerLogParameters logParameters) {
      List<Level> handlerLevels = new ArrayList<>();
      if (logParameters.consoleOutputEnabled()) {
         handlerLevels.add(parameterStringToJuliLogLevel(logParameters.consoleLogLevel()));
      }

      if (fileLoggerEnabled(logParameters)) {
         handlerLevels.add(parameterStringToJuliLogLevel(logParameters.fileLogLevel()));
      }

      return handlerLevels;
   }

   private static AsyncFileHandler newSimpleFileHandler(WebServerLogParameters logParameters) {
      AsyncFileHandler fileHandler = new AsyncFileHandler(
         logParameters.directory().toString(), logParameters.prefix() + ".", logParameters.suffix(), logParameters.maxDays()
      );
      fileHandler.setFormatter(getSimpleFormatter());
      fileHandler.setLevel(parameterStringToJuliLogLevel(logParameters.fileLogLevel()));
      return fileHandler;
   }

   private static AsyncFileHandler newStructuredFileHandler(WebServerLogParameters logParameters) {
      AsyncFileHandler fileHandler = new AsyncFileHandler(logParameters.directory().toString(), "web-server-", "-structured.log", logParameters.maxDays());
      fileHandler.setFormatter(getStructuredFormatter());
      fileHandler.setLevel(parameterStringToJuliLogLevel(logParameters.fileLogLevel()));
      return fileHandler;
   }

   private static TomcatJuliConfigurator.StdoutConsoleHandler newConsoleHandler(WebServerLogParameters logParameters) {
      Level consoleLevel = parameterStringToJuliLogLevel(logParameters.consoleLogLevel());
      return new TomcatJuliConfigurator.StdoutConsoleHandler(getConsoleFormatter(logParameters), consoleLevel);
   }

   private static void setupSimpleFormatter(WebServerLogParameters logParameters) {
      if (logParameters.simpleFileOutputEnabled() || hasSimpleConsoleOutput(logParameters)) {
         System.setProperty("java.util.logging.SimpleFormatter.format", logParameters.pattern());
      }
   }

   private static Formatter getConsoleFormatter(WebServerLogParameters logParameters) {
      return switch (logParameters.consoleFormat()) {
         case SIMPLE -> getSimpleFormatter();
         case STRUCTURED -> getStructuredFormatter();
         default -> throw new MatchException(null, null);
      };
   }

   private static Formatter getSimpleFormatter() {
      return new SimpleFormatter();
   }

   private static Formatter getStructuredFormatter() {
      return new WebserverLogStructuredFormatter();
   }

   private static boolean hasSimpleConsoleOutput(WebServerLogParameters logParameters) {
      return logParameters.consoleOutputEnabled() && logParameters.consoleFormat() == LogFormat.SIMPLE;
   }

   static Level parameterStringToJuliLogLevel(String logLevel) {
      return switch (logLevel) {
         case "ERROR" -> Level.SEVERE;
         case "WARN" -> Level.WARNING;
         case "INFO" -> Level.INFO;
         case "DEBUG" -> Level.CONFIG;
         case "TRACE" -> Level.ALL;
         default -> throw new IllegalArgumentException("Invalid log level specified: " + logLevel);
      };
   }

   private TomcatJuliConfigurator() {
      throw new AssertionError();
   }

   static class StdoutConsoleHandler extends StreamHandler {
      StdoutConsoleHandler(Formatter formatter, Level logLevel) {
         super(System.out, formatter);
         this.setLevel(logLevel);
      }

      @Override
      public void publish(LogRecord record) {
         super.publish(record);
         this.flush();
      }

      @Override
      public void close() {
         this.flush();
      }
   }
}
