package com.airlock.iam.plugin.configeditor.field;

import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.configeditor.ConfigEditorApplication;
import com.airlock.iam.plugin.configeditor.data.ConfigurableProperty;
import com.airlock.iam.plugin.configeditor.data.PropertyAdapter;
import com.airlock.iam.plugin.configeditor.ui.dialog.ChangePasswordDialog;
import com.airlock.iam.plugin.configeditor.util.GuiResources;
import com.airlock.iam.plugin.framework.misc.processor.PasswordPropertyProcessor;
import com.airlock.iam.plugin.impl.PropertyExternalFileUtils;
import com.airlock.iam.sensitivevalues.application.service.SensitiveValuesProvider;
import com.vaadin.event.MouseEvents.ClickEvent;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Component;
import com.vaadin.ui.Image;
import com.vaadin.ui.Notification;
import com.vaadin.ui.UI;
import com.vaadin.ui.Notification.Type;
import com.vaadin.v7.data.Property;
import com.vaadin.v7.data.Property.ReadOnlyException;
import com.vaadin.v7.data.util.converter.Converter.ConversionException;
import com.vaadin.v7.ui.AbstractField;
import com.vaadin.v7.ui.CustomField;
import com.vaadin.v7.ui.TextField;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;

public class MedusaPasswordField extends MedusaField<StringProperty, PasswordPropertyProcessor.PasswordObject> {
   private static final String UNSCRAMBLED_SECRET = "******************************";
   private static final String SCRAMBLED_SECRET = "[OBFUSCATED] ******************************";
   private static final String CHANGE_SECRET_VALUE_TOOLTIP = "Change secret value.";
   private final Image changePasswordIcon = this.makeIcon(GuiResources.ICON_CHANGE_PASSWORD, "Change secret value.", "with-hand", this);

   @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR", justification = "See ALIAM-15315")
   public MedusaPasswordField(PropertyAdapter<StringProperty> propertyAdapter) {
      super(propertyAdapter);
      this.changePasswordIcon.addStyleName("changePassword");
      this.getFieldLayout().addComponent(this.changePasswordIcon);
      this.getFieldLayout().setComponentAlignment(this.changePasswordIcon, Alignment.MIDDLE_LEFT);
   }

   @Override
   protected Component initContent() {
      if (!this.getConfigurableProperty().isMandatory()) {
         this.getFieldLayout().addComponent(this.getUndefinePropertyIcon());
         this.getFieldLayout().setComponentAlignment(this.getUndefinePropertyIcon(), Alignment.MIDDLE_LEFT);
      }

      return super.initContent();
   }

   @Override
   public AbstractField<PasswordPropertyProcessor.PasswordObject> createInnerField() {
      return new MedusaPasswordField.PasswordObjectField();
   }

   public void setPropertyDataSource(Property newDataSource) {
      super.setPropertyDataSource(newDataSource);
      this.updateUiAndValidate();
   }

   protected void setInternalValue(PasswordPropertyProcessor.PasswordObject newValue) {
      AbstractField<PasswordPropertyProcessor.PasswordObject> innerField = this.getInnerField();
      innerField.setReadOnly(false);
      innerField.setValue(newValue);
      this.updateUiAndValidate();
      innerField.setReadOnly(true);
   }

   public PasswordPropertyProcessor.PasswordObject getValue() {
      return null;
   }

   @Override
   public void click(ClickEvent event) {
      super.click(event);
      if (event.getSource() == this.getInheritIcon()) {
         this.setInternalValue((PasswordPropertyProcessor.PasswordObject)this.getConfigurableProperty().getValue());
      } else if (event.getSource() == this.getOverrideIcon()) {
         PasswordPropertyProcessor.PasswordObject password = (PasswordPropertyProcessor.PasswordObject)this.getConfigurableProperty().getValue();
         this.setInternalValue(password);
      } else if (event.getSource() == this.changePasswordIcon) {
         this.changePassword();
      } else if (event.getSource() == this.getUndefinePropertyIcon()) {
         this.resetPassword();
      }
   }

   @Override
   protected void updateUI(ConfigurableProperty property) {
      this.addCommonIconsForProperty(property);
      this.getInnerField().setReadOnly(true);
      boolean visible = !property.isReadOnly();
      this.changePasswordIcon.setVisible(visible);
   }

   private void changePassword() {
      String initValue = ((PasswordPropertyProcessor.PasswordObject)this.getInnerField().getValue()).getPassword();
      ChangePasswordDialog.StorageMode initMode = this.getInitialStorageMode(initValue);
      if (initMode == ChangePasswordDialog.StorageMode.EXTERNAL) {
         initValue = StringUtils.trim(Strings.CS.removeStart(initValue, "[EXTERNAL]"));
      }

      ConfigurationEnvironment instanceEnvironment = this.getApplication().getInstanceEnvironment();
      ChangePasswordDialog dialog = new ChangePasswordDialog(
         this.getConfigurableProperty().getDisplayName(), initMode, initValue, instanceEnvironment, this.getApplication().getEnvironmentSelection()
      );
      dialog.addCloseListener(e -> {
         String confirmedNewPassword = dialog.getConfirmedNewValue();
         if (confirmedNewPassword == null) {
            this.getInnerField().setReadOnly(true);
         } else {
            ChangePasswordDialog.StorageMode storageMode = dialog.getStoredAs();
            this.updatePassword(confirmedNewPassword, storageMode);
         }
      });
   }

   private ChangePasswordDialog.StorageMode getInitialStorageMode(String initValue) {
      if (!this.getSensitiveValuesProvider().isPresent() || !StringUtils.isBlank(initValue) && !initValue.startsWith("[EXTERNAL]")) {
         return !StringUtils.isBlank(initValue) && !"[OBFUSCATED] ******************************".equals(initValue)
            ? ChangePasswordDialog.StorageMode.PLAIN
            : ChangePasswordDialog.StorageMode.MASKED;
      } else {
         return ChangePasswordDialog.StorageMode.EXTERNAL;
      }
   }

   private void updatePassword(String confirmedNewPassword, ChangePasswordDialog.StorageMode mode) {
      ConfigurableProperty configurableProperty = this.getConfigurableProperty();
      configurableProperty.setReadOnly(false);
      PasswordPropertyProcessor.PasswordObject.PasswordObjectBuilder passwordObjectBuilder = PasswordPropertyProcessor.PasswordObject.builder()
         .password(confirmedNewPassword);
      if (PropertyExternalFileUtils.isStringInExternalFile(confirmedNewPassword)) {
         passwordObjectBuilder.externalFile(true);
      }

      if (mode == ChangePasswordDialog.StorageMode.MASKED) {
         passwordObjectBuilder.scrambled(true);
      } else if (mode == ChangePasswordDialog.StorageMode.EXTERNAL) {
         Optional<SensitiveValuesProvider> provider = this.getSensitiveValuesProvider();
         Optional<String> sensitiveValue = provider.flatMap(p -> p.getSensitiveValue(confirmedNewPassword));
         passwordObjectBuilder.externalStorageId(Optional.of(confirmedNewPassword)).password(sensitiveValue.orElse(null));
      }

      PasswordPropertyProcessor.PasswordObject passwordObject = passwordObjectBuilder.build();
      configurableProperty.setValue(passwordObject);
      configurableProperty.setReadOnly(configurableProperty.determineReadOnlyState());
      this.setInternalValue(passwordObject);
      Notification.show(configurableProperty.getDisplayName() + " updated.", Type.TRAY_NOTIFICATION);
   }

   private Optional<SensitiveValuesProvider> getSensitiveValuesProvider() {
      return ((ConfigEditorApplication)UI.getCurrent()).getConfigurationDictionary().getSensitiveValuesProvider();
   }

   private void resetPassword() {
      this.setInternalValue((PasswordPropertyProcessor.PasswordObject)null);
   }

   @Override
   protected boolean isUndefineButtonVisible() {
      return super.isUndefineButtonVisible() && ((PasswordPropertyProcessor.PasswordObject)this.getConfigurableProperty().getValue()).getPassword() != null;
   }

   protected boolean shouldHideErrors() {
      return this.isRequired()
         && ((PasswordPropertyProcessor.PasswordObject)this.getInnerField().getValue()).getPassword() == null
         && this.getComponentError() == null;
   }

   private static class PasswordObjectField extends CustomField<PasswordPropertyProcessor.PasswordObject> {
      private final TextField field = new TextField();

      protected Component initContent() {
         this.setSizeFull();
         this.field.setWidth("100%");
         this.field.setImmediate(true);
         this.field.setBuffered(false);
         this.field.setNullRepresentation("");
         this.field.setReadOnly(true);
         return this.field;
      }

      public void setValue(PasswordPropertyProcessor.PasswordObject newFieldValue) throws ReadOnlyException, ConversionException {
         if (newFieldValue == null || newFieldValue.isEmpty()) {
            this.field.setValue(null);
         } else if (newFieldValue.getExternalStorageId().isPresent()) {
            this.field.setValue("[EXTERNAL] " + newFieldValue.getExternalStorageId().get());
         } else if (newFieldValue.isScrambled()) {
            this.field.setValue("[OBFUSCATED] ******************************");
         } else if (newFieldValue.isExternalFile()) {
            this.field.setValue(newFieldValue.getPassword());
         } else {
            this.field.setValue("******************************");
         }
      }

      public PasswordPropertyProcessor.PasswordObject getValue() {
         return PasswordPropertyProcessor.PasswordObject.builder().password((String)this.field.getValue()).build();
      }

      public Class<? extends PasswordPropertyProcessor.PasswordObject> getType() {
         return PasswordPropertyProcessor.PasswordObject.class;
      }

      public void setReadOnly(boolean readOnly) {
         super.setReadOnly(readOnly);
         this.field.setReadOnly(readOnly);
      }
   }
}
