package com.airlock.iam.servicecontainer.app.application.configuration.task;

import com.airlock.iam.common.api.domain.model.persistency.exception.NotFoundException;
import com.airlock.iam.common.api.domain.model.persistency.exception.NotUniqueException;
import com.airlock.iam.common.api.domain.model.persistency.exception.PersisterException;
import com.airlock.iam.common.api.domain.model.user.PersistentUser;
import com.airlock.iam.common.api.domain.service.email.EmailService;
import com.airlock.iam.common.api.domain.service.email.EmailServiceException;
import com.airlock.iam.core.application.configuration.contextdata.DateAndTimeContextDataItemNameConfig;
import com.airlock.iam.core.application.configuration.contextdata.StringContextDataItemNameConfig;
import com.airlock.iam.core.misc.persistency.AuthUserBean;
import com.airlock.iam.core.misc.persistency.UserIterator;
import com.airlock.iam.core.misc.persistency.UserPersister;
import com.airlock.iam.core.misc.renderer.TextRenderer;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
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.PluginProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;

@Plugin(
   description = "Sends an email notification to all users returned by the specified iterator. After the notification has been sent successfully, the current date is written to the specified user attribute."
)
@Groups(
   groupSequence = {"Email Settings", "Persister Settings", "User Attribute Settings"},
   openGroups = {"Email Settings", "Persister Settings", "User Attribute Settings"}
)
public class EmailNotificationTask extends AbstractUserIteratorTask {
   private static final ELogger LOG = ELoggerFactory.getLogger(EmailNotificationTask.class);
   private static final String DEFAULT_CONTEXT_DATA_ATTRIBUTE_SENT_DATE = "inactivity_reminder_sent_date";
   private static final String DEFAULT_CONTEXT_DATA_ATTRIBUTE_EMAIL = "email";
   private static final String DEFAULT_CONTEXT_DATA_ATTRIBUTE_GIVENNAME = "givenname";
   private static final String DEFAULT_CONTEXT_DATA_ATTRIBUTE_SURNAME = "surname";
   private static final String DEFAULT_CONTEXT_DATA_ATTRIBUTE_LANGUAGE = "language";
   static final String GROUP_NAME_EMAIL_SETTINGS = "Email Settings";
   static final String GROUP_NAME_PERSISTER_SETTINGS = "Persister Settings";
   static final String GROUP_NAME_ATTRIBUTE_SETTINGS = "User Attribute Settings";
   @PluginProperty(description = "The Email Service plugin used to send emails.", optional = false)
   @GroupProperty(groupIdentifier = "Email Settings")
   private EmailService emailService;
   @PluginProperty(description = "The subject renderer is used to create the language-dependent email subject.", optional = false)
   @GroupProperty(groupIdentifier = "Email Settings")
   private TextRenderer subjectRenderer;
   @PluginProperty(description = "The content renderer is used to create the language-dependent email content.", optional = false)
   @GroupProperty(groupIdentifier = "Email Settings")
   private TextRenderer contentRenderer;
   @BooleanProperty(
      displayName = "Content Is HTML",
      description = "Defines whether the email content should be treated as HTML or plain text. This flag affects the MIME type of the email body.",
      defaultValue = "false",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Email Settings")
   private boolean contentIsHtml;
   @PluginProperty(description = "The User Persister plugin used to read user data and write the date when the email was sent.", optional = false)
   @GroupProperty(groupIdentifier = "Persister Settings")
   private UserPersister userPersister;
   @PluginProperty(
      description = "The User Iterator defines the set of users a notification email is sent to. It has to be configured to return correct user set.",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Persister Settings")
   private UserIterator userIterator;
   @PluginProperty(
      description = "Selects the user context attribute to store the date and time when the notification email was sent. Make sure it is configured in the configured user persister.<br>If not configured, the context data field with the name 'inactivity_reminder_sent_date' is used.",
      optional = false
   )
   @GroupProperty(groupIdentifier = "User Attribute Settings")
   private DateAndTimeContextDataItemNameConfig notificationSentDateContextAttribute;
   @PluginProperty(
      description = "Selects the user context attribute to read the recipient's email address from.<br>If not configured, the context data field with the name 'email' is used.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "User Attribute Settings")
   private StringContextDataItemNameConfig emailAddressContextAttribute;
   @PluginProperty(
      description = "Selects the user context attribute to read the recipient's given name from. This is used for the human-readable name in the \"to:\" field, but has no effect on the data used for subject or body.<br>If not configured, the context data field with the name 'givenname' is used.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "User Attribute Settings")
   private StringContextDataItemNameConfig givennameContextAttribute;
   @PluginProperty(
      description = "Selects the user context attribute to read the recipient's surname from. This is used for the human-readable name in the \"to:\" field, but has no effect on the data used for subject or body.<br>If not configured, the context data field with the name 'surname' is used.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "User Attribute Settings")
   private StringContextDataItemNameConfig surnameContextAttribute;
   @PluginProperty(
      description = "Selects the user context attribute to read the recipient's correspondence language from.<br>If not configured, the context data field with the name 'language' is used.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "User Attribute Settings")
   private StringContextDataItemNameConfig correspondanceLanguageContextAttribute;
   private EmailNotificationTask.Configuration configuration;

   public EmailNotificationTask(
      EmailService emailService,
      TextRenderer subjectRenderer,
      TextRenderer contentRenderer,
      boolean contentIsHtml,
      UserPersister userPersister,
      UserIterator userIterator,
      DateAndTimeContextDataItemNameConfig notificationSentDateContextAttribute,
      StringContextDataItemNameConfig emailAddressContextAttribute,
      StringContextDataItemNameConfig givennameContextAttribute,
      StringContextDataItemNameConfig surnameContextAttribute,
      StringContextDataItemNameConfig correspondanceLanguageContextAttribute
   ) {
      this.emailService = emailService;
      this.subjectRenderer = subjectRenderer;
      this.contentRenderer = contentRenderer;
      this.contentIsHtml = contentIsHtml;
      this.userPersister = userPersister;
      this.userIterator = userIterator;
      this.notificationSentDateContextAttribute = notificationSentDateContextAttribute;
      this.emailAddressContextAttribute = emailAddressContextAttribute;
      this.givennameContextAttribute = givennameContextAttribute;
      this.surnameContextAttribute = surnameContextAttribute;
      this.correspondanceLanguageContextAttribute = correspondanceLanguageContextAttribute;
   }

   @Override
   protected ELogger getLogger() {
      return LOG;
   }

   @Override
   protected synchronized UserIteratorTaskConfiguration getTaskConfiguration() {
      return this.configuration;
   }

   @Override
   protected void handleUser(AuthUserBean user, UserIteratorTaskConfiguration config, long startTime) {
      try {
         if (this.sendEmailNotificationToUser(user, (EmailNotificationTask.Configuration)config)) {
            this.setSentDate(user, (EmailNotificationTask.Configuration)config);
         }
      } catch (PersisterException e) {
         this.getLogger()
            .error(
               this.getLogType(),
               "error in " + this.getTaskName() + " while handling uid \"" + user.getName() + "\" (" + e.getMessage() + "). Skipping user. ",
               e
            );
      } catch (EmailServiceException e) {
         this.getLogger().error(this.getLogType(), "email notification could not be sent to user with uid \"" + user.getName() + "\". Skipping user.", e);
      }
   }

   @Override
   protected boolean shouldHandleInvalidUsers() {
      return false;
   }

   private boolean sendEmailNotificationToUser(PersistentUser user, EmailNotificationTask.Configuration currentConfig) throws EmailServiceException {
      InternetAddress toAddress = this.getToAddress(user, currentConfig);
      if (toAddress == null) {
         return false;
      }

      String language = (String)user.getContextDataValue(currentConfig.getCorrespondanceLanguageContextAttribute());
      Map<String, Object> parameters = user.getContextData();
      String subject = currentConfig.getSubjectRenderer().renderToString(language, parameters);
      String content = currentConfig.getContentRenderer().renderToString(language, parameters);
      boolean isHtml = currentConfig.isContentHtml();
      currentConfig.getEmailService().sendEmail(List.of(toAddress), List.of(), List.of(), subject, content, isHtml);
      return true;
   }

   private InternetAddress getToAddress(PersistentUser user, EmailNotificationTask.Configuration currentConfig) {
      String email = (String)user.getContextData().get(currentConfig.getEmailAddressContextAttribute());
      if (StringUtils.isEmpty(email)) {
         return null;
      }

      String givenname = (String)user.getContextDataValue(currentConfig.getGivennameContextAttribute());
      String surname = (String)user.getContextDataValue(currentConfig.getSurnameContextAttribute());
      StringBuilder fullName = new StringBuilder();
      if (givenname != null) {
         fullName.append(givenname);
      }

      if (givenname != null && surname != null) {
         fullName.append(' ');
      }

      if (surname != null) {
         fullName.append(surname);
      }

      InternetAddress emailAddress = null;

      try {
         emailAddress = new InternetAddress(email);
         emailAddress.setPersonal(fullName.toString());
      } catch (AddressException e) {
         this.getLogger().warn(this.getLogType(), "invalid email address for user '" + user.getName() + "'");
         this.getLogger().debug("invalid email address '" + email + "' for user '" + user.getName() + "'");
      } catch (UnsupportedEncodingException e) {
         this.getLogger().warn(this.getLogType(), "could not set the personal of the email address", e);
      }

      return emailAddress;
   }

   private void setSentDate(PersistentUser user, EmailNotificationTask.Configuration currentConfig) throws NotFoundException, NotUniqueException, PersisterException {
      user.putContextDataValue(currentConfig.getNotificationSentDateAttribute(), new Date());
      currentConfig.getUserPersister().makeUserPersistent(user);
   }

   @AfterConfigActivation
   synchronized void afterConfigActivation() {
      this.configuration = new EmailNotificationTask.Configuration(
         this.emailService,
         this.subjectRenderer,
         this.contentRenderer,
         this.contentIsHtml,
         this.userPersister,
         this.userIterator,
         this.notificationSentDateContextAttribute,
         this.emailAddressContextAttribute,
         this.givennameContextAttribute,
         this.surnameContextAttribute,
         this.correspondanceLanguageContextAttribute
      );
   }

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

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

   static final class Configuration implements UserIteratorTaskConfiguration {
      private final EmailService emailService;
      private final TextRenderer subjectRenderer;
      private final TextRenderer contentRenderer;
      private final boolean contentIsHtml;
      private final UserPersister userPersister;
      private final UserIterator userIterator;
      private final DateAndTimeContextDataItemNameConfig notificationSentDateContextAttribute;
      private final StringContextDataItemNameConfig emailAddressContextAttribute;
      private final StringContextDataItemNameConfig givennameContextAttribute;
      private final StringContextDataItemNameConfig surnameContextAttribute;
      private final StringContextDataItemNameConfig correspondanceLanguageContextAttribute;

      Configuration(
         EmailService emailService,
         TextRenderer subjectRenderer,
         TextRenderer contentRenderer,
         boolean contentIsHtml,
         UserPersister userPersister,
         UserIterator userIterator,
         DateAndTimeContextDataItemNameConfig notificationSentDateContextAttribute,
         StringContextDataItemNameConfig emailAddressAttributeName,
         StringContextDataItemNameConfig givennameAttributeName,
         StringContextDataItemNameConfig surnameAttributeName,
         StringContextDataItemNameConfig correspondanceLanguageAttributeName
      ) {
         this.subjectRenderer = subjectRenderer;
         this.contentRenderer = contentRenderer;
         this.contentIsHtml = contentIsHtml;
         this.userPersister = userPersister;
         this.userIterator = userIterator;
         this.notificationSentDateContextAttribute = notificationSentDateContextAttribute;
         this.emailService = emailService;
         this.emailAddressContextAttribute = emailAddressAttributeName;
         this.givennameContextAttribute = givennameAttributeName;
         this.surnameContextAttribute = surnameAttributeName;
         this.correspondanceLanguageContextAttribute = correspondanceLanguageAttributeName;
      }

      public TextRenderer getSubjectRenderer() {
         return this.subjectRenderer;
      }

      public TextRenderer getContentRenderer() {
         return this.contentRenderer;
      }

      public boolean isContentHtml() {
         return this.contentIsHtml;
      }

      @Override
      public UserPersister getUserPersister() {
         return this.userPersister;
      }

      @Override
      public UserIterator getUserIterator() {
         return this.userIterator;
      }

      public String getNotificationSentDateAttribute() {
         return this.notificationSentDateContextAttribute == null
            ? "inactivity_reminder_sent_date"
            : this.notificationSentDateContextAttribute.getContextDataName();
      }

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

      public String getEmailAddressContextAttribute() {
         return this.emailAddressContextAttribute == null ? "email" : this.emailAddressContextAttribute.getContextDataName();
      }

      public String getGivennameContextAttribute() {
         return this.givennameContextAttribute == null ? "givenname" : this.givennameContextAttribute.getContextDataName();
      }

      public String getSurnameContextAttribute() {
         return this.surnameContextAttribute == null ? "surname" : this.surnameContextAttribute.getContextDataName();
      }

      public String getCorrespondanceLanguageContextAttribute() {
         return this.correspondanceLanguageContextAttribute == null ? "language" : this.correspondanceLanguageContextAttribute.getContextDataName();
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class EmailNotificationTaskBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private EmailService emailService;
      @SuppressFBWarnings(justification = "generated code")
      private TextRenderer subjectRenderer;
      @SuppressFBWarnings(justification = "generated code")
      private TextRenderer contentRenderer;
      @SuppressFBWarnings(justification = "generated code")
      private boolean contentIsHtml;
      @SuppressFBWarnings(justification = "generated code")
      private UserPersister userPersister;
      @SuppressFBWarnings(justification = "generated code")
      private UserIterator userIterator;
      @SuppressFBWarnings(justification = "generated code")
      private DateAndTimeContextDataItemNameConfig notificationSentDateContextAttribute;
      @SuppressFBWarnings(justification = "generated code")
      private StringContextDataItemNameConfig emailAddressContextAttribute;
      @SuppressFBWarnings(justification = "generated code")
      private StringContextDataItemNameConfig givennameContextAttribute;
      @SuppressFBWarnings(justification = "generated code")
      private StringContextDataItemNameConfig surnameContextAttribute;
      @SuppressFBWarnings(justification = "generated code")
      private StringContextDataItemNameConfig correspondanceLanguageContextAttribute;

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

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

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask.EmailNotificationTaskBuilder subjectRenderer(TextRenderer subjectRenderer) {
         this.subjectRenderer = subjectRenderer;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask.EmailNotificationTaskBuilder contentRenderer(TextRenderer contentRenderer) {
         this.contentRenderer = contentRenderer;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask.EmailNotificationTaskBuilder contentIsHtml(boolean contentIsHtml) {
         this.contentIsHtml = contentIsHtml;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask.EmailNotificationTaskBuilder userPersister(UserPersister userPersister) {
         this.userPersister = userPersister;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask.EmailNotificationTaskBuilder userIterator(UserIterator userIterator) {
         this.userIterator = userIterator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask.EmailNotificationTaskBuilder notificationSentDateContextAttribute(
         DateAndTimeContextDataItemNameConfig notificationSentDateContextAttribute
      ) {
         this.notificationSentDateContextAttribute = notificationSentDateContextAttribute;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask.EmailNotificationTaskBuilder emailAddressContextAttribute(StringContextDataItemNameConfig emailAddressContextAttribute) {
         this.emailAddressContextAttribute = emailAddressContextAttribute;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask.EmailNotificationTaskBuilder givennameContextAttribute(StringContextDataItemNameConfig givennameContextAttribute) {
         this.givennameContextAttribute = givennameContextAttribute;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask.EmailNotificationTaskBuilder surnameContextAttribute(StringContextDataItemNameConfig surnameContextAttribute) {
         this.surnameContextAttribute = surnameContextAttribute;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask.EmailNotificationTaskBuilder correspondanceLanguageContextAttribute(
         StringContextDataItemNameConfig correspondanceLanguageContextAttribute
      ) {
         this.correspondanceLanguageContextAttribute = correspondanceLanguageContextAttribute;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailNotificationTask build() {
         return new EmailNotificationTask(
            this.emailService,
            this.subjectRenderer,
            this.contentRenderer,
            this.contentIsHtml,
            this.userPersister,
            this.userIterator,
            this.notificationSentDateContextAttribute,
            this.emailAddressContextAttribute,
            this.givennameContextAttribute,
            this.surnameContextAttribute,
            this.correspondanceLanguageContextAttribute
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "EmailNotificationTask.EmailNotificationTaskBuilder(emailService="
            + this.emailService
            + ", subjectRenderer="
            + this.subjectRenderer
            + ", contentRenderer="
            + this.contentRenderer
            + ", contentIsHtml="
            + this.contentIsHtml
            + ", userPersister="
            + this.userPersister
            + ", userIterator="
            + this.userIterator
            + ", notificationSentDateContextAttribute="
            + this.notificationSentDateContextAttribute
            + ", emailAddressContextAttribute="
            + this.emailAddressContextAttribute
            + ", givennameContextAttribute="
            + this.givennameContextAttribute
            + ", surnameContextAttribute="
            + this.surnameContextAttribute
            + ", correspondanceLanguageContextAttribute="
            + this.correspondanceLanguageContextAttribute
            + ")";
      }
   }
}
