package com.airlock.iam.login.app.infrastructure.web;

import com.airlock.iam.base.domain.service.uri.IamUriBuilder;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import org.apache.commons.lang3.Strings;

public class SpaCacheBuster {
   static final String SPA_CACHE_BUSTER_RELOAD = "SPA_CACHE_BUSTER_RELOAD";
   private static final ELogger LOG = ELoggerFactory.getLogger(SpaCacheBuster.class);
   private static final Pattern JS_FILE = Pattern.compile("\\.js$");
   private static final Pattern DEFAULT_JS_FILE = Pattern.compile("assets/airlock/js/iam.js");
   private final Map<String, String> fileIdentifierToHashedFileLocation = new ConcurrentHashMap<>();

   public void addCacheBusterHash(String pathToFile, HttpServletRequest request) {
      String pathToFileWithCacheBust = this.getPathToFileWithCacheBust(pathToFile, request);
      request.setAttribute("cacheBust", pathToFileWithCacheBust);
   }

   private String getPathToFileWithCacheBust(String pathToFile, HttpServletRequest request) {
      this.reloadCacheBuster(request);
      String fileIdentifier = pathToFile;
      Optional<String> uiResourceSetName = getUiResourceSetName(request);
      if (uiResourceSetName.isPresent() && !isPathToDefaultJsFile(pathToFile)) {
         fileIdentifier = IamUriBuilder.fromPath(uiResourceSetName.get()).path(pathToFile).build().getPath();
      }

      return this.fileIdentifierToHashedFileLocation
         .computeIfAbsent(fileIdentifier, id -> this.retrieveHashedFileLocation(pathToFile, uiResourceSetName, request, id));
   }

   private void reloadCacheBuster(HttpServletRequest request) {
      ServletContext servletContext = request.getServletContext();
      Optional<Object> spaCacheBusterReloadFlag = Optional.ofNullable(servletContext.getAttribute("SPA_CACHE_BUSTER_RELOAD"));
      servletContext.removeAttribute("SPA_CACHE_BUSTER_RELOAD");
      spaCacheBusterReloadFlag.ifPresent(flag -> this.fileIdentifierToHashedFileLocation.clear());
   }

   private static Optional<String> getUiResourceSetName(HttpServletRequest request) {
      return Optional.ofNullable(request.getAttribute("uiResourceSetName")).filter(String.class::isInstance).map(String.class::cast);
   }

   private static boolean isPathToDefaultJsFile(String pathToFile) {
      return DEFAULT_JS_FILE.matcher(pathToFile).matches();
   }

   private String retrieveHashedFileLocation(String pathToFile, Optional<String> uiResourceSetName, HttpServletRequest request, String fileIdentifier) {
      String internalPathToFile = IamUriBuilder.fromPath(Strings.CS.removeStart("/ui", "/")).path(pathToFile).build().getPath();
      if (!uiResourceSetName.isEmpty() && !isPathToDefaultJsFile(pathToFile)) {
         String fallbackHashedFileLocation = computeHashedFileLocation(request, internalPathToFile, pathToFile, pathToFile);
         String internalPathToUiResourceSetFile = IamUriBuilder.fromPath(Strings.CS.removeStart("/ui", "/")).path(fileIdentifier).build().getPath();
         return computeHashedFileLocation(request, internalPathToUiResourceSetFile, pathToFile, fallbackHashedFileLocation);
      } else {
         return computeHashedFileLocation(request, internalPathToFile, pathToFile, pathToFile);
      }
   }

   @SuppressFBWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE", "NP_LOAD_OF_KNOWN_NULL_VALUE"})
   private static String computeHashedFileLocation(HttpServletRequest request, String internalPathToFile, String pathToFile, String fallbackHashedFileLocation) {
      try (InputStream jsFile = request.getServletContext().getResourceAsStream(internalPathToFile)) {
         if (jsFile == null) {
            LOG.debug(
               String.format(
                  "Reverting to fallback JavaScript file at '%s' since the file at '%s' does not exist.", fallbackHashedFileLocation, internalPathToFile
               )
            );
            return fallbackHashedFileLocation;
         } else {
            HashCode fileHash = ByteSource.wrap(ByteStreams.toByteArray(jsFile)).hash(Hashing.farmHashFingerprint64());
            return insertHash(pathToFile, fileHash);
         }
      } catch (IOException e) {
         LOG.debug(
            String.format(
               "Reverting to fallback JavaScript file at '%s'. Unable to compute hash of file at '%s' since the path does not exist.",
               fallbackHashedFileLocation,
               internalPathToFile
            )
         );
         return fallbackHashedFileLocation;
      }
   }

   private static String insertHash(String fileIdentifier, HashCode hash) {
      return JS_FILE.matcher(fileIdentifier).replaceFirst("." + hash.toString() + ".js");
   }
}
