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

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import jakarta.servlet.Filter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.catalina.Context;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.WebResourceSet;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.webresources.DirResourceSet;
import org.apache.catalina.webresources.FileResourceSet;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.tomcat.util.descriptor.web.ApplicationParameter;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import org.apache.tomcat.util.http.CookieProcessor;
import org.apache.tomcat.util.scan.StandardJarScanner;

final class ContextConfigurator {
   static void setDefaultValues(Context context, CookieProcessor cookieProcessor) {
      StandardContext standardContext = (StandardContext)context;
      standardContext.setFailCtxIfServletStartFails(true);
      standardContext.setReloadable(false);
      standardContext.setAntiResourceLocking(false);
      standardContext.setCookieProcessor(cookieProcessor);
      standardContext.setManager(createStandardManager());
   }

   private static StandardManager createStandardManager() {
      StandardManager standardManager = new StandardManager();
      standardManager.setPathname(null);
      return standardManager;
   }

   static void setContextFile(Context context, List<Path> layoutDirs) {
      layoutDirs.stream()
         .map(directory -> directory.resolve("META-INF/context.xml"))
         .filter(contextFile -> Files.exists(contextFile))
         .findFirst()
         .ifPresent(contextFile -> setConfigFile(context, contextFile));
   }

   private static void setConfigFile(Context context, Path contextFile) {
      try {
         context.setConfigFile(contextFile.toUri().toURL());
      } catch (MalformedURLException e) {
         throw new IllegalStateException(e);
      }
   }

   static void addFilter(Context context, Class<? extends Filter> filter, String urlPattern) {
      FilterDef def = new FilterDef();
      def.setFilterClass(filter.getName());
      def.setFilterName(filter.getName());
      context.addFilterDef(def);
      FilterMap map = new FilterMap();
      map.setFilterName(filter.getName());
      map.addURLPattern(urlPattern);
      context.addFilterMap(map);
   }

   static void setLog4jConfiguration(Context context, List<URI> log4jConfigs) {
      if (!log4jConfigs.isEmpty()) {
         String value = Lists.reverse(log4jConfigs).stream().map(URI::toString).collect(Collectors.joining(","));
         context.addApplicationParameter(parameter("log4jConfiguration", value));
      }
   }

   static void setSpringLog4jConfiguration(Context context, List<URI> log4jConfigs) {
      if (!log4jConfigs.isEmpty()) {
         List<String> fileNames = log4jConfigs.stream().map(URI::toString).toList();
         String baseXml = fileNames.stream().filter(name -> name.contains("base.xml")).findFirst().orElseGet(fileNames::getFirst);
         context.addApplicationParameter(parameter("logging.config", baseXml));
         String additionalXmls = fileNames.stream().filter(name -> !name.equals(baseXml)).collect(Collectors.joining(","));
         if (!additionalXmls.isEmpty()) {
            context.addApplicationParameter(parameter("logging.log4j2.config.override", additionalXmls));
         }
      }
   }

   public static ApplicationParameter parameter(String name, String value) {
      ApplicationParameter parameter = new ApplicationParameter();
      parameter.setName(name);
      parameter.setValue(value);
      parameter.setOverride(false);
      return parameter;
   }

   static void setJarScanner(Context context) {
      StandardJarScanner scanner = new StandardJarScanner();
      scanner.setScanManifest(false);
      scanner.setScanClassPath(false);
      context.setJarScanner(scanner);
   }

   static void addToWebappClasspath(Context context, List<Path> classpath) {
      WebResourceRoot resourcesRoot = Optional.ofNullable(context.getResources()).orElseGet(() -> new StandardRoot(context));

      for (Path entry : classpath) {
         webResourceSet(resourcesRoot, entry).ifPresent(resourcesRoot::addPreResources);
      }

      context.setResources(resourcesRoot);
   }

   private static Optional<WebResourceSet> webResourceSet(WebResourceRoot root, Path entry) {
      if (Files.notExists(entry)) {
         return Optional.empty();
      }

      try {
         entry = entry.toRealPath();
      } catch (IOException e) {
         throw new IllegalStateException(e);
      }

      if (Files.isDirectory(entry)) {
         return Optional.of(new DirResourceSet(root, "/WEB-INF/classes/", entry.toString(), "/"));
      }

      Path fileName = (Path)Preconditions.checkNotNull(entry.getFileName(), "Resource entry has no file name: " + entry);
      return Optional.of(new FileResourceSet(root, "/WEB-INF/lib/" + fileName, entry.toString(), "/"));
   }

   static void addLayoutDirectories(Context context, List<Path> layoutDirs) {
      for (Path path : layoutDirs) {
         addDirectoryPreResource(context, path, "/");
      }
   }

   static void addSpaCustomizationResource(Context context, Path path, String mount) {
      if (Files.isDirectory(path)) {
         addDirectoryPreResource(context, path, mount);
      } else {
         addSpaCustomizationZipResource(context, path, mount);
      }
   }

   static void addSpaResourceSets(Context context, Path directory, String mount) {
      WebResourceRoot resourceRoot = getOrCreateWebResourceRoot(context);
      resourceRoot.addPreResources(new SpaResourceSets(resourceRoot, mount, directory.toString(), "/"));
   }

   private static void addDirectoryPreResource(Context context, Path directory, String mount) {
      WebResourceRoot resourceRoot = getOrCreateWebResourceRoot(context);
      resourceRoot.addPreResources(new DirResourceSet(resourceRoot, mount, directory.toString(), "/"));
   }

   private static void addSpaCustomizationZipResource(Context context, Path zip, String mount) {
      WebResourceRoot resourceRoot = getOrCreateWebResourceRoot(context);
      resourceRoot.addPreResources(new SpaCustomizationZipResourceSet(resourceRoot, mount, zip));
   }

   private static WebResourceRoot getOrCreateWebResourceRoot(Context context) {
      return Optional.ofNullable(context.getResources()).orElseGet(() -> {
         StandardRoot standardRoot = new StandardRoot(context);
         context.setResources(standardRoot);
         return standardRoot;
      });
   }

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