content: package core.manager;
  
  import com.github.windpapi4j.HResultException;
  import com.github.windpapi4j.InitializationFailedException;
  import com.github.windpapi4j.WinAPICallFailedException;
  import com.github.windpapi4j.WinDPAPI;
  import com.github.windpapi4j.WinDPAPI.CryptProtectFlag;
  import config.profile.ConfigProfile;
  import config.profile.ConnProfile;
  import config.profile.SqlColProfile;
  import config.security.BCFipsConfig;
  import config.security.ContainerConfig;
  import config.security.PassConfig;
  import config.yaml.YamlConfig;
  import core.manager.ConstantManager.Profile;
  import core.parameter.ConnectionBuilder;
  import excp.SqlColMetadataException;
  import gui.model.ContainerType;
  import gui.model.EncryptionType;
  import gui.model.RegistryKey;
  import java.security.GeneralSecurityException;
  import java.util.AbstractMap;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  import java.util.Optional;
  import java.util.TreeMap;
  import javax.crypto.SecretKey;
  import javax.inject.Inject;
  import javax.inject.Named;
  import javax.inject.Singleton;
  import lombok.Getter;
  import lombok.Setter;
  import lombok.extern.slf4j.Slf4j;
  import profile.IProfile;
  import profile.OracleEE;
  import profile.OracleEE10g;
  import profile.OracleEEObject;
  import profile.OracleSE;
  import profile.Postgres;
  import profile.Postgres96;
  
  @Slf4j
  @Singleton
  public class ConfigurationManager {
      private YamlConfig yamlConfig;
      private PassConfig passConfig;
      private BCFipsConfig bcFipsConfig;
      private ContainerConfig containerConfig;
      private TreeMap<String, ConfigProfile> configList;
  
      @Getter @Setter private ConfigProfile currentConfiguration;
      @Getter @Setter private String configurationName;
      @Getter @Setter private IProfile iProfile;
  
      @Inject
      public ConfigurationManager(YamlConfig yamlConfig,
                                  PassConfig passConfig,
                                  BCFipsConfig bcFipsConfig,
                                  ContainerConfig containerConfig,
                                  @Named("ConfigList") TreeMap<String, ConfigProfile> configList) {
          this.yamlConfig = yamlConfig;
          this.passConfig = passConfig;
          this.bcFipsConfig = bcFipsConfig;
          this.containerConfig = containerConfig;
          this.configList = configList;
      }
  
      public void loadCurrentConfiguration(String configurationName) {
          ConfigProfile configProfile = getConnProfileList().stream()
                  .filter(e -> e.getConfigName().equalsIgnoreCase(configurationName))
                  .findAny().get();
  
          configProfile.setRunning(true);
          loadConfigToFile(configProfile);
  
          setIProfile(getProfileImpl(configProfile.getConnProfile().getProfileName()));
  
          setConfigurationName(configurationName);
          setCurrentConfiguration(configProfile);
      }
  
      public void loadSqlColumnMetadata(List<SqlColProfile> profilesDb) throws SqlColMetadataException {
         Optional<List<SqlColProfile>> profileCurr = Optional.ofNullable(getCurrentConfiguration().getSqlColProfileList());
  
         if (!profileCurr.isPresent()) {
             getCurrentConfiguration().setSqlColProfileList(profilesDb);
             loadConfigToFile(getCurrentConfiguration());
         } else {
             if (profilesDb.size() != profileCurr.get().size()) {
                 throw new SqlColMetadataException("ASH sql column metadata changes detected.. " +
                         "Create the new configuration profile!");
             }
         }
      }
  
      public void loadConfigToFile(ConfigProfile configuration) {
          yamlConfig.saveConfigToFile(configuration);
      }
  
      public List<ConfigProfile> getConnProfileList() {
          return (List<ConfigProfile>) new ArrayList(configList.values());
      }
  
      public void deleteConfig(String configurationName) {
          configList.remove(configurationName);
          yamlConfig.deleteConfig(configurationName);
          yamlConfig.loadConfigsFromFs();
      }
  
      public ConnectionBuilder getConnectionParameters(String connName) {
          Map.Entry<String,ConfigProfile> other = new AbstractMap.SimpleImmutableEntry<>(connName, new ConfigProfile());
          other.getValue().setConfigName(connName);
          other.getValue().setConnProfile(new ConnProfile());
          other.getValue().getConnProfile().setPassword("");
  
          Map.Entry<String, ConfigProfile> cfg = configList.entrySet().stream()
                  .filter(e -> e.getValue().getConfigName().equalsIgnoreCase(connName))
                  .findAny().orElse(other);
  
          ConnProfile connOut = cfg.getValue().getConnProfile();
  
          return new ConnectionBuilder.Builder(connName)
                  .userName(connOut.getUserName())
                  .password(getPassword(cfg.getValue()))
                  .url(connOut.getUrl())
                  .jar(connOut.getJar())
                  .profile(connOut.getProfileName())
                  .initialLoading(String.valueOf(cfg.getValue().getInitialLoading()))
                  .rawRetainDays(String.valueOf(cfg.getValue().getRawRetainDays()))
                  .olapRetainDays(String.valueOf(cfg.getValue().getOlapRetainDays()))
                  .containerType(cfg.getValue().getContainerType())
                  .encryptionType(cfg.getValue().getEncryptionType())
                  .build();
      }
  
      public void saveConnection(ConnectionBuilder connIn) {
          Map.Entry<String, ConfigProfile> other = new AbstractMap.SimpleImmutableEntry<>("", new ConfigProfile());
          other.getValue().setConnProfile(new ConnProfile());
          other.getValue().setConfigName(connIn.getConnectionName());
  
          Map.Entry<String, ConfigProfile> cfg = configList.entrySet().stream()
                  .filter(e -> e.getValue().getConfigName().equalsIgnoreCase(connIn.getConnectionName()))
                  .findAny().orElse(other);
  
          try {
            savePassword(connIn, cfg);
          } catch (GeneralSecurityException e) {
            log.error(e.getMessage());
            throw new RuntimeException(e);
          }
          connIn.cleanPassword();
  
          ConnProfile connOut = cfg.getValue().getConnProfile();
  
          connOut.setConnName(connIn.getConnectionName());
          connOut.setUserName(connIn.getUserName());
          connOut.setUrl(connIn.getUrl());
          connOut.setJar(connIn.getJar());
          connOut.setProfileName(connIn.getProfile());
          connOut.setDriver(connIn.getDriverName());
  
          cfg.getValue().setInitialLoading(Integer.parseInt(connIn.getInitialLoading()));
          cfg.getValue().setRawRetainDays(getRawRetainDays());
          cfg.getValue().setOlapRetainDays(getOlapRetainDays());
          cfg.getValue().setEncryptionType(connIn.getEncryptionType());
          cfg.getValue().setContainerType(connIn.getContainerType());
  
          yamlConfig.saveConfigToFile(cfg.getValue());
      }
  
      private void savePassword(ConnectionBuilder connIn, Map.Entry<String, ConfigProfile> cfg) throws GeneralSecurityException {
        EncryptionType encryptionTypeCurrent = connIn.getEncryptionType();
        ContainerType containerTypeCurrent = connIn.getContainerType();
  
        ConnProfile connOut = cfg.getValue().getConnProfile();
  
        // Clean values from previous configuration
        ContainerType containerTypePrev = cfg.getValue().getContainerType();
        EncryptionType encryptionTypePrev = cfg.getValue().getEncryptionType();
        if (containerTypeCurrent != null && encryptionTypeCurrent != null
            && ContainerType.CONFIGURATION.equals(containerTypePrev)
            && containerTypeCurrent.equals(containerTypePrev)
            && !encryptionTypeCurrent.equals(encryptionTypePrev)) {
          cfg.getValue().setKey(null);
          cfg.getValue().setIv(null);
          connOut.setPassword(null);
        }
  
        // Save current values
        if (EncryptionType.PBE.equals(encryptionTypeCurrent)) {
          String passwordEncrypted = passConfig.encrypt(connIn.getPassword());
          if (ContainerType.DPAPI.equals(containerTypeCurrent)) {
            if(WinDPAPI.isPlatformSupported()) {
              try {
                WinDPAPI winDPAPI = WinDPAPI.newInstance(CryptProtectFlag.CRYPTPROTECT_UI_FORBIDDEN);
                byte[] cipherTextBytes = winDPAPI.protectData(passwordEncrypted.getBytes());
                cfg.getValue().setKey(containerConfig.convertByteToString(cipherTextBytes));
  
                cfg.getValue().setIv(null);
                connOut.setPassword(null);
              } catch (InitializationFailedException | WinAPICallFailedException e) {
                throw new RuntimeException(e);
              }
            } else {
              throw new RuntimeException("Windows Data Protection API (DPAPI) as secure container is not supported");
            }
          } else if (ContainerType.REGISTRY.equals(containerTypeCurrent)) {
            containerConfig.setRegistryValue(cfg.getValue().getConfigName(), RegistryKey.PASSWORD, passwordEncrypted);
  
            cfg.getValue().setKey(null);
            cfg.getValue().setIv(null);
            connOut.setPassword(null);
          } else if (ContainerType.CONFIGURATION.equals(containerTypeCurrent)) {
            connOut.setPassword(passwordEncrypted);
  
            cfg.getValue().setKey(null);
            cfg.getValue().setIv(null);
          }
        } else if (EncryptionType.AES.equals(encryptionTypeCurrent)) {
          SecretKey secretKey = bcFipsConfig.generateKey();
          byte[][] passwordEncrypted = bcFipsConfig.cfbEncrypt(secretKey, connIn.getPassword().getBytes());
  
          if (ContainerType.DPAPI.equals(containerTypeCurrent)) {
            if(WinDPAPI.isPlatformSupported()) {
              try {
                WinDPAPI winDPAPI = WinDPAPI.newInstance(CryptProtectFlag.CRYPTPROTECT_UI_FORBIDDEN);
                byte[] cipherKeyBytes = winDPAPI.protectData(containerConfig.convertSecretKeyToString(secretKey).getBytes());
                byte[] cipherIvBytes = winDPAPI.protectData(containerConfig.convertByteToString(passwordEncrypted[0]).getBytes());
                byte[] cipherPassEncBytes = winDPAPI.protectData(containerConfig.convertByteToString(passwordEncrypted[1]).getBytes());
  
                cfg.getValue().setKey(containerConfig.convertByteToString(cipherKeyBytes));
                cfg.getValue().setIv(containerConfig.convertByteToString(cipherIvBytes));
                connOut.setPassword(containerConfig.convertByteToString(cipherPassEncBytes));
              } catch (InitializationFailedException | WinAPICallFailedException e) {
                throw new RuntimeException(e);
              }
            } else {
              throw new RuntimeException("Windows Data Protection API (DPAPI) as secure container is not supported");
            }
          } else if (ContainerType.REGISTRY.equals(containerTypeCurrent)) {
            String cipherKey = containerConfig.convertSecretKeyToString(secretKey);
            String cipherIv = containerConfig.convertByteToString(passwordEncrypted[0]);
            String cipherPassEnc = containerConfig.convertByteToString(passwordEncrypted[1]);
  
            containerConfig.setRegistryValue(cfg.getValue().getConfigName(), RegistryKey.KEY, cipherKey);
            containerConfig.setRegistryValue(cfg.getValue().getConfigName(), RegistryKey.IV, cipherIv);
            containerConfig.setRegistryValue(cfg.getValue().getConfigName(), RegistryKey.PASSWORD, cipherPassEnc);
  
            cfg.getValue().setKey(null);
            cfg.getValue().setIv(null);
            connOut.setPassword(null);
          } else if (ContainerType.CONFIGURATION.equals(containerTypeCurrent)) {
            cfg.getValue().setKey(containerConfig.convertSecretKeyToString(secretKey));
            cfg.getValue().setIv(containerConfig.convertByteToString(passwordEncrypted[0]));
            connOut.setPassword(containerConfig.convertByteToString(passwordEncrypted[1]));
          }
        }
      }
  
      public String getPassword(ConfigProfile configProfile) {
        EncryptionType encryptionType = configProfile.getEncryptionType();
        ContainerType containerType = configProfile.getContainerType();
  
        if (encryptionType == null && containerType == null) {
            try {
                return passConfig.decrypt(configProfile.getConnProfile().getPassword());
            } catch (Exception e) {
                log.error("Config name: " + configProfile.getConfigName());
                log.error("The most likely reason is running the configuration on another computer, delete or recreate it");
                throw new RuntimeException(e);
            }
        }
  
        String password = "";
  
        if (EncryptionType.PBE.equals(encryptionType)) {
          if (ContainerType.DPAPI.equals(containerType)) {
            if(WinDPAPI.isPlatformSupported()) {
              try {
                WinDPAPI winDPAPI = WinDPAPI.newInstance(CryptProtectFlag.CRYPTPROTECT_UI_FORBIDDEN);
                byte[] decryptedBytes = winDPAPI.unprotectData(containerConfig.convertStringToByte(configProfile.getKey()));
                password = passConfig.decrypt(new String(decryptedBytes));
              } catch (InitializationFailedException | WinAPICallFailedException e) {
                throw new RuntimeException(e);
              }
            } else {
              throw new RuntimeException("Windows Data Protection API (DPAPI) as secure container is not supported");
            }
          } else if (ContainerType.REGISTRY.equals(containerType)) {
            password = passConfig.decrypt(containerConfig.getRegistryValue(configProfile.getConfigName(), RegistryKey.PASSWORD));
          } else if (ContainerType.CONFIGURATION.equals(containerType)) {
            password = passConfig.decrypt(configProfile.getConnProfile().getPassword());
          }
        } else if (EncryptionType.AES.equals(encryptionType)) {
          if (ContainerType.DPAPI.equals(containerType)) {
              if(WinDPAPI.isPlatformSupported()) {
                try {
                  WinDPAPI winDPAPI = WinDPAPI.newInstance(CryptProtectFlag.CRYPTPROTECT_UI_FORBIDDEN);
  
                  String secretKeyStr = new String(winDPAPI.unprotectData(containerConfig.convertStringToByte(configProfile.getKey())));
                  String ivStr = new String(winDPAPI.unprotectData(containerConfig.convertStringToByte(configProfile.getIv())));
                  String pwdStr = new String(winDPAPI.unprotectData(containerConfig.convertStringToByte(configProfile.getConnProfile().getPassword())));
  
                  try {
                    password = new String(bcFipsConfig.cfbDecrypt(containerConfig.convertStringToSecretKey(secretKeyStr),
                        containerConfig.convertStringToByte(ivStr), containerConfig.convertStringToByte(pwdStr)));
                  } catch (GeneralSecurityException e) {
                    throw new RuntimeException(e);
                  }
  
                } catch (InitializationFailedException | WinAPICallFailedException e) {
                    log.error("Config name: " + configProfile.getConfigName());
                    log.error("The most likely reason is running the configuration on another computer, delete or recreate it");
                    throw new RuntimeException(e);
                }
              } else {
                throw new RuntimeException("Windows Data Protection API (DPAPI) as secure container is not supported");
              }
          } else if (ContainerType.REGISTRY.equals(containerType)) {
            String secretKeyStr = containerConfig.getRegistryValue(configProfile.getConfigName(), RegistryKey.KEY);
            String ivStr = containerConfig.getRegistryValue(configProfile.getConfigName(), RegistryKey.IV);
            String pwdStr = containerConfig.getRegistryValue(configProfile.getConfigName(), RegistryKey.PASSWORD);
  
            try {
              password = new String(bcFipsConfig.cfbDecrypt(containerConfig.convertStringToSecretKey(secretKeyStr),
                  containerConfig.convertStringToByte(ivStr), containerConfig.convertStringToByte(pwdStr)));
            } catch (GeneralSecurityException e) {
              throw new RuntimeException(e);
            }
          } else if (ContainerType.CONFIGURATION.equals(containerType)) {
            SecretKey secretKey = containerConfig.convertStringToSecretKey(configProfile.getKey());
            byte[] iv = containerConfig.convertStringToByte(configProfile.getIv());
            byte[] passwordEncrypted = containerConfig.convertStringToByte(configProfile.getConnProfile().getPassword());
  
            try {
              password = new String(bcFipsConfig.cfbDecrypt(secretKey, iv, passwordEncrypted));
            } catch (GeneralSecurityException e) {
              throw new RuntimeException(e);
            }
          }
  
        }
  
        return password;
      }
  
      public int getRawRetainDays() {
          return ConstantManager.RETAIN_DAYS_MAX;
      }
  
      public int getOlapRetainDays() {
          return ConstantManager.RETAIN_DAYS_MAX;
      }
  
      public void closeCurrentProfile() {
          if (currentConfiguration != null){
              getCurrentConfiguration().setRunning(false);
              loadConfigToFile(getCurrentConfiguration());
          }
      }
  
      public IProfile getProfileImpl(String profileName) {
        Profile profile = Profile.getValue(profileName);
        if (profile == null) {
          throw new IllegalArgumentException("Invalid profile name: " + profileName);
        }
        switch (profile) {
          case OracleEE:
            return new OracleEE();
          case OracleEE10g:
            return new OracleEE10g();
          case OracleEEObject:
            return new OracleEEObject();
          case OracleSE:
            return new OracleSE();
          case Postgres:
            return new Postgres();
          case Postgres96:
            return new Postgres96();
          default :
            throw new IllegalArgumentException("Unsupported profile name: " + profileName);
        }
      }
  
  }
  