package com.airlock.iam.admin.application.configuration.mtan;

import com.airlock.iam.admin.api.domain.model.user.AuthenticationMethods;
import com.airlock.iam.admin.domain.model.token.TokenController;
import com.airlock.iam.core.misc.impl.authen.mtan.MtanSettings;
import com.airlock.iam.core.misc.renderer.SecretLetterRenderer;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
import com.airlock.iam.plugin.api.annotation.config.Groups;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.property.BooleanProperty;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringListProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.BooleanPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;

@Plugin(
   description = "Token controller plugin to handle mTAN (SMS) tokens. In addition to adding, changing and removing phone numbers, this plugin offers the following features:<ul>\t<li>Send OTP code to user's phone (e.g. to authenticate user calling help desk or to verify the number entered manually).</li>\t<li>When changing the phone number, a notification about this can be sent to the old phone number.</li>\t<li>A notification about the registration of a phone number may be sent to the corresponding number.</li>\t<li>Part (or all of) the configured phone numbers may be masked, so the administrator does not see entire numbers.</li></ul>",
   displayName = "mTAN/SMS Token Controller"
)
@Groups(groupSequence = {"Basic Settings", "SMS Notification", "Advanced Settings"}, openGroups = "Basic Settings")
public class MtanTokenController implements TokenController {
   public static final String ID = "MTAN";
   static final String SMS_NOTIFICATION_SETTINGS = "SMS Notification";
   @PluginProperty(
      description = "Global mTAN settings configuring all kind of aspects concerning mTAN.",
      displayName = "mTAN Settings",
      globalPluginReference = "globalMtanSettings",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private MtanSettings mtanSettings;
   private static final String MTAN_SETTINGS_FIELD = "mtanSettings";
   @BooleanProperty(
      description = "Set to true to inform the old phone number when changing a user's phone number. The message defined by property \"notifyOldNumberTemplate\".",
      defaultValue = "false",
      optional = true
   )
   @GroupProperty(groupIdentifier = "SMS Notification")
   private boolean notifyOldNumber;
   private static final String NOTIFY_OLD_NUMBER_FIELD = "notifyOldNumber";
   @StringProperty(
      description = "Template text used to inform the old phone number when a new phone number is registered. The template may contain the token \"$OLDNUMBER$\". It is replaced by the old phone number. The template may contain the token \"$NEWNUMBER$\". It is replaced by the new phone number. <p>This property is only relevant if \"notifyOldNumber\" is true.</p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "SMS Notification")
   private String notifyOldNumberTemplate;
   private static final String NOTIFY_OLD_NUMBER_TEMPLATE_FIELD = "notifyOldNumberTemplate";
   @PluginProperty(
      description = "If configured, allows to generate an mTAN IAK directly from the admin tool. The administrator requires the 'generateMtanIak' right to perform the operation. Also note that whenever this property is configured, the mTAN Handler configured in the mTAN Settings must be configured such that it supports immediate generation of IAKs.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private SecretLetterRenderer secretLetterRenderer;
   @TokenController.TokenControllerId
   @StringProperty(
      description = "Identifier for the credential. This is used as value in the authentication method field in the persistence layer. Make sure this value is the same (for the same credential) in all Airlock IAM components.<p>Make sure the identifier is unique among all configured token controllers.</p><p>The identifier is also used as key to translate the display name of this token controller. The key is assembled as follows: <code>edituserpage.cred.XYZ.title</code> (where XYZ is the identifier).</p>",
      optional = true,
      suggestedValues = {"MTAN", "SMS"},
      defaultValue = "MTAN"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String identifier;
   @BooleanProperty(
      description = "Set this flag to true to automatically order the credential when it is added to a user.",
      optional = true,
      defaultValue = "false"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean autoOrderCredential;
   @BooleanProperty(
      description = "Set this flag to true to automatically order the credential if the user is created. This flag is not required if the \"mTAN Letter User Event Listener\" is configured in the \"Database User Persister\". It is no issue to have configured both.",
      optional = true,
      defaultValue = "false"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean autoOrderForNewUsers;
   @BooleanProperty(
      description = "Set this flag to true to display the letter attributes (used to display generation and delivery dates of credential letters).",
      optional = true,
      defaultValue = "false"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean showLetterAttributes;
   @StringListProperty(
      description = "Defines a list of context data properties that are considered to part of the credential data. The specified context data properties are deleted (set to \"null\") when the credential is deleted (in addition to the credential data field, the serial number, the letter attribute and the order flag). <br/>Make sure, the underlying persister plugin is configured to persist the specified context data properties.",
      optional = true,
      minElements = 1,
      exampleElements = {"cert_tbs", "tok_additional_data"}
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private List<String> deletePropertiesWithCredential;

   public boolean showLetterAttributes() {
      return this.showLetterAttributes;
   }

   public String getOtpMessageTemplate() {
      return this.mtanSettings.getNotificationSmsTemplate();
   }

   public boolean isNotifyOldNumber() {
      return this.notifyOldNumber;
   }

   public boolean supportsLabel() {
      return this.mtanSettings.getMtanHandler().supportsLabel();
   }

   public boolean isImmediateIakLetterGenerationSupported() {
      return this.mtanSettings.getMtanHandler().isImmediateIakLetterGenerationSupported() && this.secretLetterRenderer != null;
   }

   public Optional<String> getNotifyOldNumberTemplate() {
      return Optional.ofNullable(this.notifyOldNumberTemplate);
   }

   public int maxNumberOfTokens() {
      return this.mtanSettings.getMtanHandler().maxNumberOfTokens();
   }

   public boolean isTestSmsEnabled() {
      return this.mtanSettings.getSmsGateway() != null && StringUtils.isNotBlank(this.getOtpMessageTemplate());
   }

   public boolean isEditable() {
      return this.mtanSettings.isAdminMayEditPhoneNumber();
   }

   public Optional<SecretLetterRenderer> getSecretLetterRenderer() {
      return Optional.ofNullable(this.secretLetterRenderer);
   }

   @Override
   public AuthenticationMethods.AuthenticationMethodId getConfiguredId() {
      return AuthenticationMethods.AuthenticationMethodId.authMethodId(this.identifier);
   }

   @Override
   public String getTokenControllerId() {
      return "MTAN";
   }

   public MtanSettings getMtanSettings() {
      return this.mtanSettings;
   }

   public boolean isAutoOrderCredential() {
      return this.autoOrderCredential;
   }

   public boolean isAutoOrderForNewUsers() {
      return this.autoOrderForNewUsers;
   }

   public List<String> getDeletePropertiesWithCredential() {
      return this.deletePropertiesWithCredential;
   }

   @ConfigurationValidator
   public static void validateNotifyOldNumberConstraints(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      PluginConfiguration mtanSettings = PluginPropertyValueReader.effectivePluginPropertyValue(config, "mtanSettings", env, context);
      PluginConfiguration smsGatewayConfig = PluginPropertyValueReader.effectivePluginPropertyValue(mtanSettings, "smsGateway", env, context);
      PluginConfiguration generatorConfig = PluginPropertyValueReader.effectivePluginPropertyValue(mtanSettings, "otpGenerator", env, context);
      Boolean notifyOldNumber = BooleanPropertyValueReader.effectiveBooleanPropertyValue(config, "notifyOldNumber", env, context);
      String notifyOldNumberTemplate = StringPropertyValueReader.effectiveStringConfigValue(config, "notifyOldNumberTemplate", env, context);
      if (notifyOldNumber != null && notifyOldNumber) {
         if (StringUtils.isEmpty(notifyOldNumberTemplate)) {
            throw new PropertyConstraintViolationException(
               "notifyOldNumberTemplate", config, env, context, "Sending notifications to old number is enabled, but no template is configured."
            );
         }

         if (smsGatewayConfig == null) {
            throw new PropertyConstraintViolationException(
               "smsGateway", mtanSettings, env, context, "Sending notifications to old number is enabled, but no SMS gateway is configured."
            );
         }

         if (generatorConfig == null) {
            throw new PropertyConstraintViolationException(
               "otpGenerator", mtanSettings, env, context, "Sending notifications to old number is enabled, but no token generator is configured."
            );
         }
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   public static MtanTokenController.MtanTokenControllerBuilder builder() {
      return new MtanTokenController.MtanTokenControllerBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public MtanTokenController() {
   }

   @SuppressFBWarnings(justification = "generated code")
   public MtanTokenController(
      MtanSettings mtanSettings,
      boolean notifyOldNumber,
      String notifyOldNumberTemplate,
      SecretLetterRenderer secretLetterRenderer,
      String identifier,
      boolean autoOrderCredential,
      boolean autoOrderForNewUsers,
      boolean showLetterAttributes,
      List<String> deletePropertiesWithCredential
   ) {
      this.mtanSettings = mtanSettings;
      this.notifyOldNumber = notifyOldNumber;
      this.notifyOldNumberTemplate = notifyOldNumberTemplate;
      this.secretLetterRenderer = secretLetterRenderer;
      this.identifier = identifier;
      this.autoOrderCredential = autoOrderCredential;
      this.autoOrderForNewUsers = autoOrderForNewUsers;
      this.showLetterAttributes = showLetterAttributes;
      this.deletePropertiesWithCredential = deletePropertiesWithCredential;
   }

   @SuppressFBWarnings(justification = "generated code")
   public void setSecretLetterRenderer(SecretLetterRenderer secretLetterRenderer) {
      this.secretLetterRenderer = secretLetterRenderer;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class MtanTokenControllerBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private MtanSettings mtanSettings;
      @SuppressFBWarnings(justification = "generated code")
      private boolean notifyOldNumber;
      @SuppressFBWarnings(justification = "generated code")
      private String notifyOldNumberTemplate;
      @SuppressFBWarnings(justification = "generated code")
      private SecretLetterRenderer secretLetterRenderer;
      @SuppressFBWarnings(justification = "generated code")
      private String identifier;
      @SuppressFBWarnings(justification = "generated code")
      private boolean autoOrderCredential;
      @SuppressFBWarnings(justification = "generated code")
      private boolean autoOrderForNewUsers;
      @SuppressFBWarnings(justification = "generated code")
      private boolean showLetterAttributes;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> deletePropertiesWithCredential;

      @SuppressFBWarnings(justification = "generated code")
      MtanTokenControllerBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenController.MtanTokenControllerBuilder mtanSettings(MtanSettings mtanSettings) {
         this.mtanSettings = mtanSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenController.MtanTokenControllerBuilder notifyOldNumber(boolean notifyOldNumber) {
         this.notifyOldNumber = notifyOldNumber;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenController.MtanTokenControllerBuilder notifyOldNumberTemplate(String notifyOldNumberTemplate) {
         this.notifyOldNumberTemplate = notifyOldNumberTemplate;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenController.MtanTokenControllerBuilder secretLetterRenderer(SecretLetterRenderer secretLetterRenderer) {
         this.secretLetterRenderer = secretLetterRenderer;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenController.MtanTokenControllerBuilder identifier(String identifier) {
         this.identifier = identifier;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenController.MtanTokenControllerBuilder autoOrderCredential(boolean autoOrderCredential) {
         this.autoOrderCredential = autoOrderCredential;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenController.MtanTokenControllerBuilder autoOrderForNewUsers(boolean autoOrderForNewUsers) {
         this.autoOrderForNewUsers = autoOrderForNewUsers;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenController.MtanTokenControllerBuilder showLetterAttributes(boolean showLetterAttributes) {
         this.showLetterAttributes = showLetterAttributes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenController.MtanTokenControllerBuilder deletePropertiesWithCredential(List<String> deletePropertiesWithCredential) {
         this.deletePropertiesWithCredential = deletePropertiesWithCredential;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanTokenController build() {
         return new MtanTokenController(
            this.mtanSettings,
            this.notifyOldNumber,
            this.notifyOldNumberTemplate,
            this.secretLetterRenderer,
            this.identifier,
            this.autoOrderCredential,
            this.autoOrderForNewUsers,
            this.showLetterAttributes,
            this.deletePropertiesWithCredential
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanTokenController.MtanTokenControllerBuilder(mtanSettings="
            + this.mtanSettings
            + ", notifyOldNumber="
            + this.notifyOldNumber
            + ", notifyOldNumberTemplate="
            + this.notifyOldNumberTemplate
            + ", secretLetterRenderer="
            + this.secretLetterRenderer
            + ", identifier="
            + this.identifier
            + ", autoOrderCredential="
            + this.autoOrderCredential
            + ", autoOrderForNewUsers="
            + this.autoOrderForNewUsers
            + ", showLetterAttributes="
            + this.showLetterAttributes
            + ", deletePropertiesWithCredential="
            + this.deletePropertiesWithCredential
            + ")";
      }
   }
}
