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

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.misc.persistency.ExtendedUserPersister;
import com.airlock.iam.core.misc.persistency.UserIterator;
import com.airlock.iam.core.misc.persistency.UserPersister;
import com.airlock.iam.core.misc.util.config.Config;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
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.license.PluginLicenseRestriction;
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.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.api.valuereader.IntegerPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import com.airlock.iam.plugin.framework.misc.exception.MandatoryPropertyMissingException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;

@Plugin(
   description = "Server task that looks at all self-registered users and sends a reminder email to self-registered users that have never logged in after a configurable number of minutes."
)
@PluginLicenseRestriction(moduleAffiliation = "SelfRegistration")
@Groups(groupSequence = {"Basic Settings", "Advanced Settings"}, openGroups = "Basic Settings")
public class SelfRegUsersReminderTask extends AbstractSelfRegUsersTask {
   private static final ELogger LOG = ELoggerFactory.getLogger(SelfRegUsersReminderTask.class);
   @PluginProperty(
      description = "Email service through which the emails are sent if the user did never login. This property is mandatory if \"Never Logged In Minutes\" is configured.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private EmailService emailService;
   private static final String EMAIL_SERVICE = "emailService";
   @StringProperty(
      description = "<p>Name of the context data field that contains the user's email address. Used to send an email if the user did never login.</p>",
      defaultValue = "email",
      optional = true,
      exampleValues = {"email", "new_email"}
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String emailPropertyName;
   @StringProperty(
      description = "<p>Name of the context data field where it is remembered that a reminder email has been sent already. If this is not configured, an email will be sent every time the task is executed. Make sure to also configure this field in the user persister.</p>",
      optional = true,
      exampleValues = "activity_reminder_sent"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String alreadySentPropertyName;
   @StringProperty(
      description = "<p>Name of the context data field that contains the user language. Used when sending a reminder email if the user did never login.</p>",
      defaultValue = "language",
      optional = true,
      exampleValues = "language"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String languagePropertyName;
   @StringProperty(
      description = "<p>Default language to be used if none is defined for the user.</p>",
      defaultValue = "de",
      optional = true,
      exampleValues = {"de", "fr", "en"}
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String defaultLanguage;
   @StringProperty(
      description = "Resource key for the email subject. Used when sending an email if the user did never login.",
      optional = true,
      defaultValue = "selfregistration.reminder-email-subject",
      exampleValues = {"selfregistration.reminder-email-subject", "selfregistration.not-logged-in-reminder-email-subject"}
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String notLoggedInReminderEmailSubjectKey;
   @StringProperty(
      description = "Resource key for the email body (the message). Used when sending an email if the user did never login.",
      optional = true,
      defaultValue = "selfregistration.reminder-email-body",
      exampleValues = {"selfregistration.reminder-email-body", "selfregistration.not-logged-in-reminder-email-body"}
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String notLoggedInReminderEmailBodyKey;
   @StringProperty(
      description = "Language dependent string resources (e.g. texts for email subject and body) are located in property files. This setting configures the prefix of these property files. Used when sending an email if the user did never login.<p><b>Example:</b> If the value of this property is <i>strings</i>, the language dependent files must be \"<i>strings</i>_de.properties\", \"<i>strings</i>_en.properties\" and so on and the default file must be \"<i>strings</i>.properties\".</p>",
      optional = true,
      defaultValue = "strings"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String resourcesFilePrefix;

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

   @Override
   protected void doActionNotLoggedIn(PersistentUser user, String reason, UserIteratorTaskConfiguration config) throws PersisterException {
      if (this.isSendReminderEmail(user, reason)) {
         InternetAddress address = this.getEmailAddress(user);
         if (address == null) {
            return;
         }

         String lang = this.getLanguage(user);
         String subject = Config.getLanguageDependentResource(this.resourcesFilePrefix, lang, this.notLoggedInReminderEmailSubjectKey, new Object[0]);
         String body = Config.getLanguageDependentResource(this.resourcesFilePrefix, lang, this.notLoggedInReminderEmailBodyKey, new Object[0]);

         try {
            this.emailService.sendEmail(List.of(address), List.of(), List.of(), subject, body, false);
         } catch (EmailServiceException e) {
            this.getLogger().error(this.getLogType(), "Failed to send reminder email to user that did never log in: " + e.getMessage(), e);
         }

         this.setEmailHasBeenSent(user, config);
      }
   }

   private boolean isSendReminderEmail(PersistentUser user, String reason) {
      String uid = user.getName();
      if (this.hasAlreadyBeenSent(user)) {
         this.getLogger().debug("A reminder email has already been sent to user " + uid);
         return false;
      } else {
         this.getLogger().debug("Sending reminder email to user " + uid + " because: " + reason);
         return true;
      }
   }

   private void setEmailHasBeenSent(PersistentUser user, UserIteratorTaskConfiguration config) throws PersisterException {
      this.setAlreadySentFlag(user, config.getUserPersister());
      this.getLogger().debug("Successfully sent reminder email to user " + user.getName());
   }

   private boolean hasAlreadyBeenSent(PersistentUser user) {
      if (StringUtils.isBlank(this.alreadySentPropertyName)) {
         return false;
      }

      Object sent = user.getContextData().get(this.alreadySentPropertyName);
      return this.isTrue(sent);
   }

   private boolean isTrue(Object obj) {
      if (obj instanceof Boolean) {
         return (Boolean)obj;
      } else {
         return obj instanceof Number num ? num.doubleValue() == 1.0 : false;
      }
   }

   private Object getTrue(Object oldValue) {
      return oldValue instanceof Double ? 1.0 : true;
   }

   private void setAlreadySentFlag(PersistentUser user, UserPersister userPersister) throws PersisterException {
      if (!StringUtils.isBlank(this.alreadySentPropertyName)) {
         Object oldValue = user.getContextData().get(this.alreadySentPropertyName);
         user.putContextDataValue(this.alreadySentPropertyName, this.getTrue(oldValue));
         userPersister.makeUserPersistent(user);
      }
   }

   private String getLanguage(PersistentUser user) {
      if (StringUtils.isBlank(this.languagePropertyName)) {
         return this.defaultLanguage;
      }

      String lang = (String)user.getContextData().get(this.languagePropertyName);
      return StringUtils.isBlank(lang) ? this.defaultLanguage : lang;
   }

   private InternetAddress getEmailAddress(PersistentUser user) {
      String email = Objects.toString(user.getContextData().get(this.emailPropertyName), null);
      if (!StringUtils.isBlank(email) && email.contains("@")) {
         try {
            return new InternetAddress(email);
         } catch (AddressException e) {
            this.getLogger().info("Invalid email address: " + email + " exception: " + e.getMessage() + ". No reminder email sent.");
            return null;
         }
      } else {
         this.getLogger().info("No valid email address found for user: " + email + ". No reminder email sent.");
         return null;
      }
   }

   @ConfigurationValidator
   static void validateThatEmailServiceIsConfiguredIfUsingReminderForNeverLoggedIn(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      Integer neverLoggedInMinutesToCheck = IntegerPropertyValueReader.effectiveIntegerConfigValue(config, "neverLoggedInMinutes", env, context);
      if (neverLoggedInMinutesToCheck != null) {
         PluginConfiguration emailServiceConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "emailService", env, context);
         if (emailServiceConfig == null) {
            throw new MandatoryPropertyMissingException("emailService", config, env, context);
         }
      }
   }

   public SelfRegUsersReminderTask(
      ExtendedUserPersister userPersister,
      UserIterator userIterator,
      Integer neverLoggedInMinutes,
      EmailService emailService,
      String emailPropertyName,
      String alreadySentPropertyName,
      String languagePropertyName,
      String defaultLanguage,
      String notLoggedInReminderEmailSubjectKey,
      String notLoggedInReminderEmailBodyKey,
      String resourcesFilePrefix
   ) {
      super(userPersister, userIterator, neverLoggedInMinutes);
      this.emailService = emailService;
      this.emailPropertyName = emailPropertyName;
      this.alreadySentPropertyName = alreadySentPropertyName;
      this.languagePropertyName = languagePropertyName;
      this.defaultLanguage = defaultLanguage;
      this.notLoggedInReminderEmailSubjectKey = notLoggedInReminderEmailSubjectKey;
      this.notLoggedInReminderEmailBodyKey = notLoggedInReminderEmailBodyKey;
      this.resourcesFilePrefix = resourcesFilePrefix;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public SelfRegUsersReminderTask(
      EmailService emailService,
      String emailPropertyName,
      String alreadySentPropertyName,
      String languagePropertyName,
      String defaultLanguage,
      String notLoggedInReminderEmailSubjectKey,
      String notLoggedInReminderEmailBodyKey,
      String resourcesFilePrefix
   ) {
      this.emailService = emailService;
      this.emailPropertyName = emailPropertyName;
      this.alreadySentPropertyName = alreadySentPropertyName;
      this.languagePropertyName = languagePropertyName;
      this.defaultLanguage = defaultLanguage;
      this.notLoggedInReminderEmailSubjectKey = notLoggedInReminderEmailSubjectKey;
      this.notLoggedInReminderEmailBodyKey = notLoggedInReminderEmailBodyKey;
      this.resourcesFilePrefix = resourcesFilePrefix;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class SelfRegUsersReminderTaskBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ExtendedUserPersister userPersister;
      @SuppressFBWarnings(justification = "generated code")
      private UserIterator userIterator;
      @SuppressFBWarnings(justification = "generated code")
      private Integer neverLoggedInMinutes;
      @SuppressFBWarnings(justification = "generated code")
      private EmailService emailService;
      @SuppressFBWarnings(justification = "generated code")
      private String emailPropertyName;
      @SuppressFBWarnings(justification = "generated code")
      private String alreadySentPropertyName;
      @SuppressFBWarnings(justification = "generated code")
      private String languagePropertyName;
      @SuppressFBWarnings(justification = "generated code")
      private String defaultLanguage;
      @SuppressFBWarnings(justification = "generated code")
      private String notLoggedInReminderEmailSubjectKey;
      @SuppressFBWarnings(justification = "generated code")
      private String notLoggedInReminderEmailBodyKey;
      @SuppressFBWarnings(justification = "generated code")
      private String resourcesFilePrefix;

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

      @SuppressFBWarnings(justification = "generated code")
      public SelfRegUsersReminderTask.SelfRegUsersReminderTaskBuilder userPersister(ExtendedUserPersister userPersister) {
         this.userPersister = userPersister;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public SelfRegUsersReminderTask.SelfRegUsersReminderTaskBuilder neverLoggedInMinutes(Integer neverLoggedInMinutes) {
         this.neverLoggedInMinutes = neverLoggedInMinutes;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public SelfRegUsersReminderTask.SelfRegUsersReminderTaskBuilder emailPropertyName(String emailPropertyName) {
         this.emailPropertyName = emailPropertyName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelfRegUsersReminderTask.SelfRegUsersReminderTaskBuilder alreadySentPropertyName(String alreadySentPropertyName) {
         this.alreadySentPropertyName = alreadySentPropertyName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelfRegUsersReminderTask.SelfRegUsersReminderTaskBuilder languagePropertyName(String languagePropertyName) {
         this.languagePropertyName = languagePropertyName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelfRegUsersReminderTask.SelfRegUsersReminderTaskBuilder defaultLanguage(String defaultLanguage) {
         this.defaultLanguage = defaultLanguage;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelfRegUsersReminderTask.SelfRegUsersReminderTaskBuilder notLoggedInReminderEmailSubjectKey(String notLoggedInReminderEmailSubjectKey) {
         this.notLoggedInReminderEmailSubjectKey = notLoggedInReminderEmailSubjectKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelfRegUsersReminderTask.SelfRegUsersReminderTaskBuilder notLoggedInReminderEmailBodyKey(String notLoggedInReminderEmailBodyKey) {
         this.notLoggedInReminderEmailBodyKey = notLoggedInReminderEmailBodyKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelfRegUsersReminderTask.SelfRegUsersReminderTaskBuilder resourcesFilePrefix(String resourcesFilePrefix) {
         this.resourcesFilePrefix = resourcesFilePrefix;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SelfRegUsersReminderTask build() {
         return new SelfRegUsersReminderTask(
            this.userPersister,
            this.userIterator,
            this.neverLoggedInMinutes,
            this.emailService,
            this.emailPropertyName,
            this.alreadySentPropertyName,
            this.languagePropertyName,
            this.defaultLanguage,
            this.notLoggedInReminderEmailSubjectKey,
            this.notLoggedInReminderEmailBodyKey,
            this.resourcesFilePrefix
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "SelfRegUsersReminderTask.SelfRegUsersReminderTaskBuilder(userPersister="
            + this.userPersister
            + ", userIterator="
            + this.userIterator
            + ", neverLoggedInMinutes="
            + this.neverLoggedInMinutes
            + ", emailService="
            + this.emailService
            + ", emailPropertyName="
            + this.emailPropertyName
            + ", alreadySentPropertyName="
            + this.alreadySentPropertyName
            + ", languagePropertyName="
            + this.languagePropertyName
            + ", defaultLanguage="
            + this.defaultLanguage
            + ", notLoggedInReminderEmailSubjectKey="
            + this.notLoggedInReminderEmailSubjectKey
            + ", notLoggedInReminderEmailBodyKey="
            + this.notLoggedInReminderEmailBodyKey
            + ", resourcesFilePrefix="
            + this.resourcesFilePrefix
            + ")";
      }
   }
}
