package com.airlock.iam.plugin.framework.misc.template;

import com.airlock.iam.plugin.framework.domain.model.ConfigTemplateMetaData;
import com.airlock.iam.security.util.SeededSecureRandom;
import com.airlock.iam.security.util.crypto.random.AlphanumericRandomStringGenerator;
import com.airlock.iam.system.property.IamSystemProperties;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;

public class ConfigurationTemplate implements Comparable<ConfigurationTemplate> {
   private static final String INSTANCE_TAG_NAME = "@medusa-instance@";
   private static final ConfigurationTemplate.Tag SHARED_SECRET_TAG = new ConfigurationTemplate.Tag(
      "@defaultSharedSecret@", () -> AlphanumericRandomStringGenerator.generateHumanSafeAlphanumericRandomString(20)
   );
   private static final ConfigurationTemplate.Tag DB_URL_TAG = new ConfigurationTemplate.Tag("@dbUrl@", IamSystemProperties::getEmbeddedH2JdbcUrl);
   private static final ConfigurationTemplate.Tag DB_USER_TAG = new ConfigurationTemplate.Tag("@dbUser@", IamSystemProperties::getEmbeddedH2JdbcUser);
   private static final ConfigurationTemplate.Tag DB_PASSWORD_TAG = new ConfigurationTemplate.Tag(
      "@dbPassword@", IamSystemProperties::getEmbeddedH2JdbcPassword
   );
   private static final ConfigurationTemplate.Tag LOGINAPP_ENCRYPTION_KEY_TAG = new ConfigurationTemplate.Tag(
      "@loginappEncryptionKey@", () -> base64EncodedRandomWithLength(32)
   );
   private static final ConfigurationTemplate.Tag LOGINAPP_HMAC_KEY_TAG = new ConfigurationTemplate.Tag(
      "@loginappHmacKey@", () -> base64EncodedRandomWithLength(64)
   );
   private static final ConfigurationTemplate.Tag API_POLICY_SERVICE_API_KEY_REPOSITORY_ENCRYPTION_KEY_TAG = new ConfigurationTemplate.Tag(
      "@apiKeyRepositoryEncryptionKey@", () -> base64EncodedRandomWithLength(32)
   );
   private static final ConfigurationTemplate.Tag API_POLICY_SERVICE_API_KEY_REPOSITORY_HMAC_TAG = new ConfigurationTemplate.Tag(
      "@apiKeyRepositoryHmacKey@", () -> base64EncodedRandomWithLength(64)
   );
   private static final ConfigurationTemplate.Tag API_POLICY_SERVICE_JWT_HMAC_TAG = new ConfigurationTemplate.Tag(
      "@apiKeyJWTHmacKey@", () -> base64EncodedRandomWithLength(64)
   );
   private static final ConfigurationTemplate.Tag FUTURAE_SERVICE_ID_DUMMY_TAG = new ConfigurationTemplate.Tag(
      "@futuraeServiceId@", () -> "00000000-0000-0000-0000-000000000000"
   );
   private static final ConfigurationTemplate.Tag FUTURAE_AUTH_API_KEY_DUMMY_TAG = new ConfigurationTemplate.Tag("@futuraeAuthApiKey@", () -> "change-me");
   private static final ConfigurationTemplate.Tag FUTURAE_ADMIN_API_KEY_DUMMY_TAG = new ConfigurationTemplate.Tag("@futuraeAdminApiKey@", () -> "change-me");
   private static final ConfigurationTemplate.Tag STORAGE_ENCRYPTION_SECRET = new ConfigurationTemplate.Tag(
      "@storageEncryptionSecret@", () -> base64EncodedRandomWithLength(96)
   );
   private final File templateFile;
   private final ConfigTemplateMetaData metaData;

   public ConfigurationTemplate(File templateFile, ConfigTemplateMetaData metaData) {
      Objects.requireNonNull(templateFile, "template file null");
      Objects.requireNonNull(metaData, "Meta data null");
      this.templateFile = templateFile;
      this.metaData = metaData;
   }

   public ConfigTemplateMetaData getMetaData() {
      return this.metaData;
   }

   public File getTemplateFile() {
      return this.templateFile;
   }

   public File getTemplateFileWithVariablesReplaced(String instanceName) throws IOException {
      File f = File.createTempFile("tmp." + instanceName, ".xml");
      f.deleteOnExit();
      String xml = FileUtils.readFileToString(this.templateFile, StandardCharsets.UTF_8);
      String replaced = replaceTags(
         xml,
         new ConfigurationTemplate.Tag("@medusa-instance@", () -> instanceName),
         SHARED_SECRET_TAG,
         DB_URL_TAG,
         DB_USER_TAG,
         DB_PASSWORD_TAG,
         LOGINAPP_ENCRYPTION_KEY_TAG,
         LOGINAPP_HMAC_KEY_TAG,
         API_POLICY_SERVICE_API_KEY_REPOSITORY_ENCRYPTION_KEY_TAG,
         API_POLICY_SERVICE_API_KEY_REPOSITORY_HMAC_TAG,
         API_POLICY_SERVICE_JWT_HMAC_TAG,
         STORAGE_ENCRYPTION_SECRET,
         FUTURAE_SERVICE_ID_DUMMY_TAG,
         FUTURAE_AUTH_API_KEY_DUMMY_TAG,
         FUTURAE_ADMIN_API_KEY_DUMMY_TAG
      );
      FileUtils.writeStringToFile(f, replaced, StandardCharsets.UTF_8);
      return f;
   }

   private static String replaceTags(String xml, ConfigurationTemplate.Tag... tags) {
      for (ConfigurationTemplate.Tag tag : tags) {
         xml = tag.replace(xml);
      }

      return xml;
   }

   private static String base64EncodedRandomWithLength(int length) {
      return Base64.getEncoder().encodeToString(randomBytesWithLength(length));
   }

   private static byte[] randomBytesWithLength(int length) {
      byte[] result = new byte[length];
      SeededSecureRandom.getInstance().nextBytes(result);
      return result;
   }

   public int compareTo(ConfigurationTemplate that) {
      return Comparator.comparing(File::getName).compare(this.templateFile, that.templateFile);
   }

   @Override
   public boolean equals(Object obj) {
      return obj instanceof ConfigurationTemplate that ? Objects.equals(this.templateFile, that.templateFile) : false;
   }

   @Override
   public int hashCode() {
      return Objects.hashCode(this.templateFile);
   }

   private static class Tag {
      private final Pattern pattern;
      private final Supplier<String> value;

      Tag(String key, Supplier<String> value) {
         this.pattern = Pattern.compile(Pattern.quote(key));
         this.value = value;
      }

      String replace(String xml) {
         Matcher matcher = this.pattern.matcher(xml);
         return matcher.find() ? matcher.replaceAll(this.value.get()) : xml;
      }
   }
}
