package com.airlock.iam.flow.shared.application.configuration.continuation;

import com.airlock.iam.common.api.application.configuration.valueprovider.StringValueProviderConfig;
import com.airlock.iam.common.api.domain.service.email.EmailService;
import com.airlock.iam.common.application.configuration.continuation.FlowContinuationRepositoryConfig;
import com.airlock.iam.flow.api.application.configuration.selection.FlowConditionConfig;
import com.airlock.iam.flow.api.application.configuration.step.AbstractFlowStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.NonInteractiveStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.StepIdConfig;
import com.airlock.iam.flow.api.application.configuration.tag.TagConfig;
import com.airlock.iam.flow.api.application.configuration.valueprovider.ValueMapProviderConfig;
import com.airlock.iam.flow.application.configuration.flow.FlowIdConfig;
import com.airlock.iam.flow.types.api.application.configuration.step.StepConfigForPublicSelfServiceFlows;
import com.airlock.iam.flow.types.api.application.configuration.step.StepConfigForUserSelfRegFlows;
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.lifecycle.AfterConfigActivation;
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.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
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.ConfigurationException;
import com.airlock.iam.plugin.framework.domain.service.validation.DurationParser;
import com.airlock.iam.plugin.framework.domain.service.validation.DurationValidator;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Plugin(
   description = "<p>Flow step that sends an email with a link to the current user. This link allows the user to perform further\nsteps in a specified public self-service flow. Because the link contains an unguessable token, no further\nidentity verification is necessary in the target flow.</p>\n<p>Typical use-cases:\n<ul>\n<li>Password reset with identity verification using email link: Flow 1: public self-service flow with user identification step, followed by email link step.\nFlow 2: flow continuation step followed by password reset set.</li>\n<li>Email address verification after self-registration: Flow 1: user self-registration flow that locks the user, email link step at the end of the flow.\nFlow 2: Flow 2: flow continuation step followed by unlock step.</li>\n</ul></p>\n",
   displayName = "Send Email Link Step"
)
@Groups(openGroups = "Basic Settings", groupSequence = {"Basic Settings", "Advanced Settings", "Tags/Guards", "Flow Control"})
public final class SendEmailLinkStepConfig
   extends AbstractFlowStepConfig
   implements NonInteractiveStepConfig,
   StepConfigForPublicSelfServiceFlows,
   StepConfigForUserSelfRegFlows {
   public static final String SEND_EMAIL_LINK_STEP_DISPLAY_NAME = "Send Email Link Step";
   @PluginProperty(
      description = "Flow ID of the public self-service flow where the token is verified and the user can perform further steps.\nThe target flow must start with a \"Flow Continuation Step\" that interprets the token.",
      displayName = "Target Flow ID",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private FlowIdConfig targetFlowId;
   @PluginProperty(
      description = "A String Value Provider which provides the email address of the recipient. Make sure that this\nalways provides a valid email address, otherwise this step will fail.\n",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private StringValueProviderConfig recipientAddress;
   @PluginProperty(description = "Email service for sending emails.", displayName = "Email Service", optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private EmailService emailService;
   @StringProperty(
      description = "Resource key to select the localized template to generate the subject line. The localized template can\ncontain variables (e.g. <tt>${town}</tt>). All variables are defined by the Value Providers configured below.\n",
      exampleValues = {"public-self-service.email.link.subject", "password-reset.email.link.subject"},
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String subjectResourceKey;
   @StringProperty(
      description = "Resource key to select the localized template to generate the email body. The localized template can\ncontain variables (e.g. <tt>${town}</tt>). The template\tmust contain the variable \"<tt>${LINK}</tt>\"\nwhich will be replaced by the continuation link. All other variables are defined by the Value Providers configured below.\n",
      exampleValues = {"public-self-service.email.link.body", "password-reset.email.link.body"},
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String bodyResourceKey;
   @PluginProperty(description = "Configures the repository to store flow continuation data.", optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private FlowContinuationRepositoryConfig repository;
   @StringProperty(
      description = "Absolute URL that handles flow continuation links. If left empty, the IAM Loginapp UI URL is used.\nOtherwise, a custom URL can be configured with the use of the following parameters that will be replaced when the link is generated:\n<ul>\n<li><code>${token}</code> - the random continuation token</li>\n<li><code>${flowId}</code> - the ID of the target flow to continue</li>\n<li><code>${language}</code> - the current language used in the flow</li>\n</ul>",
      displayName = "Continuation URL",
      exampleValues = {
            "https://myhost.com/auth/ui/app/self-service/select/flow/${flowId}?lang=${language}&token=${token}",
            "https://myhost.com/continue/flow/${flowId}?lang=${language}&token=${token}"
      },
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String continuationUrl;
   private static final String CONTINUATION_URL_FIELD = "continuationUrl";
   @PluginListProperty(
      description = "<p>List of value map providers that are used to replace the variables in the localized template. The values providers are called in the configured order and their values are added to a map. Later providers can overwrite values from earlier providers. If no value providers are configured, the localized template should not contain any variables, since all of them would be replaced by empty strings. The link to continue the process is always added as ${TOKEN}  and doesn't have to be configured here.</p><p><strong>Security Warning</strong>: If e-mails are sent as HTML, make sure to properly escape values originating from untrusted sources (such as user input during self-registration). This can be achieved by enabling the property 'Escape Values in HTML'. <p>If a more fine-grained control is required, use a 'Transforming Value Map Provider' with an 'HTML String Escaper' to transform all values of the map. (Alternatively, if you want to transform only individual values of a map, use a 'Value Provider Map' with 'Transforming String Value Providers'.)</p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<ValueMapProviderConfig> valueProviders;
   @BooleanProperty(
      description = "<p>If enabled, the verification email will be sent as an HTML mail. Otherwise it will be sent as plain text.</p><p><strong>Security Warning</strong>: If e-mails are sent as HTML, make sure to properly escape values originating from untrusted sources (such as user input during self-registration). This can be achieved by enabling the property 'Escape Values in HTML'. <p>If a more fine-grained control is required, use a 'Transforming Value Map Provider' with an 'HTML String Escaper' to transform all values of the map. (Alternatively, if you want to transform only individual values of a map, use a 'Value Provider Map' with 'Transforming String Value Providers'.)</p>",
      displayName = "Send As HTML",
      optional = true,
      defaultValue = "false"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean sendAsHtml;
   private static final String SEND_AS_HTML_PROPERTY_NAME = "Send As HTML";
   @StringProperty(
      description = "Determines how long the generated token (and thus the link) is valid.\n<p>The duration must be specified in the format \"2d 4h 10m 5s\" (any part can be omitted).</p>\n",
      exampleValues = {"10d", "8h", "2d 12h"},
      defaultValue = "24h",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String tokenValidity;
   private static final String TOKEN_VALIDITY_FIELD = "tokenValidity";
   private Duration tokenValidityDuration;
   @BooleanProperty(
      description = "If enabled, the forward URL (if available) is stored together with the token information and\nwill be used after the target flow is completed to redirect the user.\n",
      displayName = "Store Forward URL",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private boolean storeForwardUrl;
   @BooleanProperty(
      description = "<p>HTML-escape all provided values if property Send As HTML is enabled.</p><p><strong>Security Warning</strong>: If e-mails are sent as HTML, make sure to properly escape values originating from untrusted sources (such as user input during self-registration). This can be achieved by enabling the property 'Escape Values in HTML'. <p>If a more fine-grained control is required, use a 'Transforming Value Map Provider' with an 'HTML String Escaper' to transform all values of the map. (Alternatively, if you want to transform only individual values of a map, use a 'Value Provider Map' with 'Transforming String Value Providers'.)</p>",
      displayName = "Escape Values in HTML",
      defaultValue = "true",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private boolean escapeHtmlValues;

   public Optional<String> getContinuationUrl() {
      return Optional.ofNullable(this.continuationUrl);
   }

   @ConfigurationValidator
   public static void validateTokenDuration(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      DurationValidator.validateDurationFieldIfSet(config, "tokenValidity", env, context);
   }

   @ConfigurationValidator
   public static void validateContinuationUrlField(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      ContinuationUrlValidator.validateContinuationUrl(config, "continuationUrl", env, context);
   }

   @AfterConfigActivation
   void parseTokenDuration() {
      this.tokenValidityDuration = new DurationParser(this.tokenValidity).getDuration();
   }

   public SendEmailLinkStepConfig(
      @Nullable FlowConditionConfig skipCondition,
      @Nullable FlowConditionConfig preCondition,
      boolean requiresActivation,
      List<TagConfig> tagsOnSuccess,
      @Nullable StepIdConfig stepId,
      Map<String, StepIdConfig> onFailureGotos,
      List<ValueMapProviderConfig> customResponseAttributes,
      List<ValueMapProviderConfig> customFailureResponseAttributes,
      FlowIdConfig targetFlowId,
      String continuationUrl,
      StringValueProviderConfig recipientAddress,
      EmailService emailService,
      String subjectResourceKey,
      String bodyResourceKey,
      FlowContinuationRepositoryConfig repository,
      List<ValueMapProviderConfig> valueProviders,
      boolean sendAsHtml,
      String tokenValidity,
      boolean storeForwardUrl,
      boolean escapeHtmlValues
   ) {
      super(skipCondition, preCondition, requiresActivation, tagsOnSuccess, stepId, onFailureGotos, customResponseAttributes, customFailureResponseAttributes);
      this.targetFlowId = targetFlowId;
      this.continuationUrl = continuationUrl;
      this.recipientAddress = recipientAddress;
      this.emailService = emailService;
      this.subjectResourceKey = subjectResourceKey;
      this.bodyResourceKey = bodyResourceKey;
      this.repository = repository;
      this.valueProviders = valueProviders;
      this.sendAsHtml = sendAsHtml;
      this.tokenValidity = tokenValidity;
      this.storeForwardUrl = storeForwardUrl;
      this.escapeHtmlValues = escapeHtmlValues;
   }

   public FlowIdConfig getTargetFlowId() {
      return this.targetFlowId;
   }

   public StringValueProviderConfig getRecipientAddress() {
      return this.recipientAddress;
   }

   public EmailService getEmailService() {
      return this.emailService;
   }

   public String getSubjectResourceKey() {
      return this.subjectResourceKey;
   }

   public String getBodyResourceKey() {
      return this.bodyResourceKey;
   }

   public FlowContinuationRepositoryConfig getRepository() {
      return this.repository;
   }

   public List<ValueMapProviderConfig> getValueProviders() {
      return this.valueProviders;
   }

   public boolean isSendAsHtml() {
      return this.sendAsHtml;
   }

   public String getTokenValidity() {
      return this.tokenValidity;
   }

   public Duration getTokenValidityDuration() {
      return this.tokenValidityDuration;
   }

   public boolean isStoreForwardUrl() {
      return this.storeForwardUrl;
   }

   public boolean isEscapeHtmlValues() {
      return this.escapeHtmlValues;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class SendEmailLinkStepConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private FlowConditionConfig skipCondition;
      @SuppressFBWarnings(justification = "generated code")
      private FlowConditionConfig preCondition;
      @SuppressFBWarnings(justification = "generated code")
      private boolean requiresActivation;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<TagConfig> tagsOnSuccess;
      @SuppressFBWarnings(justification = "generated code")
      private StepIdConfig stepId;
      @SuppressFBWarnings(justification = "generated code")
      private Map<String, StepIdConfig> onFailureGotos;
      @SuppressFBWarnings(justification = "generated code")
      private List<ValueMapProviderConfig> customResponseAttributes;
      @SuppressFBWarnings(justification = "generated code")
      private List<ValueMapProviderConfig> customFailureResponseAttributes;
      @SuppressFBWarnings(justification = "generated code")
      private FlowIdConfig targetFlowId;
      @SuppressFBWarnings(justification = "generated code")
      private String continuationUrl;
      @SuppressFBWarnings(justification = "generated code")
      private StringValueProviderConfig recipientAddress;
      @SuppressFBWarnings(justification = "generated code")
      private EmailService emailService;
      @SuppressFBWarnings(justification = "generated code")
      private String subjectResourceKey;
      @SuppressFBWarnings(justification = "generated code")
      private String bodyResourceKey;
      @SuppressFBWarnings(justification = "generated code")
      private FlowContinuationRepositoryConfig repository;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<ValueMapProviderConfig> valueProviders;
      @SuppressFBWarnings(justification = "generated code")
      private boolean sendAsHtml;
      @SuppressFBWarnings(justification = "generated code")
      private String tokenValidity;
      @SuppressFBWarnings(justification = "generated code")
      private boolean storeForwardUrl;
      @SuppressFBWarnings(justification = "generated code")
      private boolean escapeHtmlValues;

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

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder skipCondition(@Nullable FlowConditionConfig skipCondition) {
         this.skipCondition = skipCondition;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder preCondition(@Nullable FlowConditionConfig preCondition) {
         this.preCondition = preCondition;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder requiresActivation(boolean requiresActivation) {
         this.requiresActivation = requiresActivation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder tagOnSuccess(TagConfig tagOnSuccess) {
         if (this.tagsOnSuccess == null) {
            this.tagsOnSuccess = new ArrayList<>();
         }

         this.tagsOnSuccess.add(tagOnSuccess);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder tagsOnSuccess(Collection<? extends TagConfig> tagsOnSuccess) {
         if (tagsOnSuccess == null) {
            throw new NullPointerException("tagsOnSuccess cannot be null");
         }

         if (this.tagsOnSuccess == null) {
            this.tagsOnSuccess = new ArrayList<>();
         }

         this.tagsOnSuccess.addAll(tagsOnSuccess);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder clearTagsOnSuccess() {
         if (this.tagsOnSuccess != null) {
            this.tagsOnSuccess.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder stepId(@Nullable StepIdConfig stepId) {
         this.stepId = stepId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder onFailureGotos(Map<String, StepIdConfig> onFailureGotos) {
         this.onFailureGotos = onFailureGotos;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder customResponseAttributes(List<ValueMapProviderConfig> customResponseAttributes) {
         this.customResponseAttributes = customResponseAttributes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder customFailureResponseAttributes(
         List<ValueMapProviderConfig> customFailureResponseAttributes
      ) {
         this.customFailureResponseAttributes = customFailureResponseAttributes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder targetFlowId(FlowIdConfig targetFlowId) {
         this.targetFlowId = targetFlowId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder continuationUrl(String continuationUrl) {
         this.continuationUrl = continuationUrl;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder recipientAddress(StringValueProviderConfig recipientAddress) {
         this.recipientAddress = recipientAddress;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder emailService(EmailService emailService) {
         this.emailService = emailService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder subjectResourceKey(String subjectResourceKey) {
         this.subjectResourceKey = subjectResourceKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder bodyResourceKey(String bodyResourceKey) {
         this.bodyResourceKey = bodyResourceKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder repository(FlowContinuationRepositoryConfig repository) {
         this.repository = repository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder valueProvider(ValueMapProviderConfig valueProvider) {
         if (this.valueProviders == null) {
            this.valueProviders = new ArrayList<>();
         }

         this.valueProviders.add(valueProvider);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder valueProviders(Collection<? extends ValueMapProviderConfig> valueProviders) {
         if (valueProviders == null) {
            throw new NullPointerException("valueProviders cannot be null");
         }

         if (this.valueProviders == null) {
            this.valueProviders = new ArrayList<>();
         }

         this.valueProviders.addAll(valueProviders);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder clearValueProviders() {
         if (this.valueProviders != null) {
            this.valueProviders.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder sendAsHtml(boolean sendAsHtml) {
         this.sendAsHtml = sendAsHtml;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder tokenValidity(String tokenValidity) {
         this.tokenValidity = tokenValidity;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder storeForwardUrl(boolean storeForwardUrl) {
         this.storeForwardUrl = storeForwardUrl;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder escapeHtmlValues(boolean escapeHtmlValues) {
         this.escapeHtmlValues = escapeHtmlValues;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SendEmailLinkStepConfig build() {
         List tagsOnSuccess = switch (this.tagsOnSuccess == null ? 0 : this.tagsOnSuccess.size()) {
            case 0 -> Collections.emptyList();
            case 1 -> Collections.singletonList(this.tagsOnSuccess.get(0));
            default -> Collections.unmodifiableList(new ArrayList<>(this.tagsOnSuccess));
         };

         return new SendEmailLinkStepConfig(
            this.skipCondition,
            this.preCondition,
            this.requiresActivation,
            tagsOnSuccess,
            this.stepId,
            this.onFailureGotos,
            this.customResponseAttributes,
            this.customFailureResponseAttributes,
            this.targetFlowId,
            this.continuationUrl,
            this.recipientAddress,
            this.emailService,
            this.subjectResourceKey,
            this.bodyResourceKey,
            this.repository,
            switch (this.valueProviders == null ? 0 : this.valueProviders.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.valueProviders.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.valueProviders));
            },
            this.sendAsHtml,
            this.tokenValidity,
            this.storeForwardUrl,
            this.escapeHtmlValues
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "SendEmailLinkStepConfig.SendEmailLinkStepConfigBuilder(skipCondition="
            + this.skipCondition
            + ", preCondition="
            + this.preCondition
            + ", requiresActivation="
            + this.requiresActivation
            + ", tagsOnSuccess="
            + this.tagsOnSuccess
            + ", stepId="
            + this.stepId
            + ", onFailureGotos="
            + this.onFailureGotos
            + ", customResponseAttributes="
            + this.customResponseAttributes
            + ", customFailureResponseAttributes="
            + this.customFailureResponseAttributes
            + ", targetFlowId="
            + this.targetFlowId
            + ", continuationUrl="
            + this.continuationUrl
            + ", recipientAddress="
            + this.recipientAddress
            + ", emailService="
            + this.emailService
            + ", subjectResourceKey="
            + this.subjectResourceKey
            + ", bodyResourceKey="
            + this.bodyResourceKey
            + ", repository="
            + this.repository
            + ", valueProviders="
            + this.valueProviders
            + ", sendAsHtml="
            + this.sendAsHtml
            + ", tokenValidity="
            + this.tokenValidity
            + ", storeForwardUrl="
            + this.storeForwardUrl
            + ", escapeHtmlValues="
            + this.escapeHtmlValues
            + ")";
      }
   }
}
