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

import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.common.api.domain.model.persistency.exception.PersisterException;
import com.airlock.iam.core.misc.authen.PredefinedLockReason;
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.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.log.audit.AuditLogger;
import com.airlock.iam.log.usertrail.UserTrailLogger;
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.LongProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.servicecontainer.app.infrastructure.event.EventFactories;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.time.FastDateFormat;

@Plugin(
   description = "Server task that checks all user accounts and locks the ones that have not been used for a configured amount of time.\n<p>The task looks at the latest successful login timestamp. If it is not empty and too far in the past (see configuration properties below),\nthe account is locked. Login failures are not taken into account. Accounts marked invalid and accounts that are already locked are ignored.\n</p>\n<p>This task publishes a \"User Locked\" event for each locked user.</p>"
)
public class LockInactiveAccountsTask extends AbstractUserIteratorTask {
   private static final ELogger LOG = ELoggerFactory.getLogger(LockInactiveAccountsTask.class);
   private static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy/MM/dd HH:mm:ss");
   private static final long DEFAULT_ALLOWED_UNUSED_DAYS = 40L;
   @PluginProperty(description = "User persister plugin used to read user account data and lock it if necessary.", optional = false)
   private UserPersister userPersister;
   @PluginProperty(
      description = "The user iterator plugin used to iterate over all users.<br/>Usually this is the same as the <class>UserPersister</class>.",
      optional = false
   )
   private UserIterator userIterator;
   @LongProperty(description = "Amount of days an account may be unused before it is locked.", optional = false, minValue = 1L, exampleValues = 40L)
   private long expirationDays;
   private LockInactiveAccountsTask.ConfigBean config;

   public LockInactiveAccountsTask(UserPersister userPersister, UserIterator userIterator, long expirationDays) {
      this.userPersister = userPersister;
      this.userIterator = userIterator;
      this.expirationDays = expirationDays;
   }

   @AfterConfigActivation
   void afterConfigActivation() {
      LockInactiveAccountsTask.ConfigBean cb = new LockInactiveAccountsTask.ConfigBean(this.userPersister, this.userIterator, this.expirationDays);
      this.config = cb;
      this.getLogger().info(this.getLogType(), "Accounts may be unused for " + cb.expirationDays + " days before being locked.");
   }

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

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

   @Override
   protected void handleUser(AuthUserBean u, UserIteratorTaskConfiguration c, long startTime) {
      LockInactiveAccountsTask.ConfigBean currentConfig = (LockInactiveAccountsTask.ConfigBean)c;
      if (!u.isLocked() && u.getLatestSuccessfulLogin() != null) {
         long diffMillis = startTime - u.getLatestSuccessfulLogin().getTime();
         if (diffMillis >= TimeUnit.DAYS.toMillis(currentConfig.expirationDays)) {
            if (!u.isLocked()) {
               u.setLockReason(PredefinedLockReason.ACCOUNT_INACTIVE.getResourceKey());
               u.setLockDate(new Date());
            }

            u.setLocked();

            try {
               currentConfig.userPersister.makeUserPersistent(u);
               AuditLogger.log(
                  this.getLogType(),
                  "The account of user \""
                     + u.getName()
                     + "\" has been locked by task "
                     + LockInactiveAccountsTask.class.getSimpleName()
                     + " because the account has not been used since "
                     + DATE_FORMAT.format(u.getLatestSuccessfulLogin())
                     + " (accounts are locked after "
                     + currentConfig.expirationDays
                     + " days of inactivity)."
               );
               UserTrailLogger.log(
                  u.getName(),
                  "User locked because logged in since "
                     + DATE_FORMAT.format(u.getLatestSuccessfulLogin())
                     + " (accounts are locked after "
                     + currentConfig.expirationDays
                     + " days of inactivity)."
               );
               EventFactories.userFactory().publishUserLockedEvent(UserId.userId(u.getName()), PredefinedLockReason.ACCOUNT_INACTIVE);
            } catch (PersisterException e) {
               this.getLogger()
                  .error(
                     this.getLogType(),
                     "Error in " + this.getTaskName() + " while handling user \"" + u.getName() + "\" (" + e.getMessage() + "). Skipping user. ",
                     e
                  );
            }
         }
      }
   }

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

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

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

   private static final class ConfigBean implements UserIteratorTaskConfiguration {
      private final UserPersister userPersister;
      private final UserIterator userIterator;
      private final long expirationDays;

      private ConfigBean(UserPersister userPersister, UserIterator userIterator, long expirationDays) {
         this.userPersister = userPersister;
         this.userIterator = userIterator;
         this.expirationDays = expirationDays;
      }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class LockInactiveAccountsTaskBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private UserPersister userPersister;
      @SuppressFBWarnings(justification = "generated code")
      private UserIterator userIterator;
      @SuppressFBWarnings(justification = "generated code")
      private long expirationDays;

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public LockInactiveAccountsTask.LockInactiveAccountsTaskBuilder expirationDays(long expirationDays) {
         this.expirationDays = expirationDays;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LockInactiveAccountsTask build() {
         return new LockInactiveAccountsTask(this.userPersister, this.userIterator, this.expirationDays);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "LockInactiveAccountsTask.LockInactiveAccountsTaskBuilder(userPersister="
            + this.userPersister
            + ", userIterator="
            + this.userIterator
            + ", expirationDays="
            + this.expirationDays
            + ")";
      }
   }
}
