package com.airlock.iam.admin.application.service.token.oathotp;

import com.airlock.iam.admin.api.domain.model.administrator.CurrentAdministrator;
import com.airlock.iam.admin.application.configuration.oathotp.OathOtpTokenController;
import com.airlock.iam.admin.domain.model.administrator.permissions.AdminappAction;
import com.airlock.iam.admin.domain.model.token.oathotp.OathOtpActivationLetter;
import com.airlock.iam.admin.domain.model.token.oathotp.OathOtpActivationLetterOrder;
import com.airlock.iam.admin.domain.service.access.AdminappAccessController;
import com.airlock.iam.base.api.domain.model.exception.EntityNotFoundException;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
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.common.application.service.token.oathotp.CommonOathOtpTokenServiceImpl;
import com.airlock.iam.common.domain.model.token.oathotp.OathOtpToken;
import com.airlock.iam.core.misc.impl.persistency.CredentialData;
import com.airlock.iam.core.misc.impl.tokenverifier.oathotp.OathOtpSettings;
import com.airlock.iam.core.misc.impl.tokenverifier.oathotp.OtpCredBeanData;
import com.airlock.iam.core.misc.persistency.CredentialBean;
import com.airlock.iam.log.api.signing.SigningLogger;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import java.util.Optional;
import java.util.function.UnaryOperator;

public class AdminOathOtpTokenServiceImpl {
   private final AdminappAccessController accessController;
   private final SigningLogger signingLogger;
   private final CurrentAdministrator administrator;
   private final OathOtpTokenController oathOtpTokenController;
   private final CommonOathOtpTokenServiceImpl commonOathOtpTokenService;

   @Inject
   public AdminOathOtpTokenServiceImpl(
      AdminappAccessController accessController,
      SigningLogger signingLogger,
      CurrentAdministrator administrator,
      OathOtpTokenController tokenController,
      CommonOathOtpTokenServiceImpl commonOathOtpTokenService
   ) {
      this.accessController = accessController;
      this.signingLogger = signingLogger;
      this.administrator = administrator;
      this.commonOathOtpTokenService = commonOathOtpTokenService;
      this.oathOtpTokenController = tokenController;
   }

   public OathOtpToken createToken(UserId userId) throws ViolationException {
      OathOtpToken token = this.commonOathOtpTokenService.createToken(userId);
      return !this.accessController.isActionAllowed(AdminappAction.VIEW_OATH_OTP_TOKEN_SECRET)
         ? OathOtpToken.of(
            token.getUserId(), Optional.empty(), token.getOrganizationLabel(), token.getVerificationInformation(), this.oathOtpSettings().isTimeBased()
         )
         : token;
   }

   public Optional<OathOtpToken> retrieveToken(UserId userId) {
      CredentialBean credBean = this.getCredentialBean(userId);
      return !this.hasSecret(credBean)
         ? Optional.empty()
         : Optional.of(
            OathOtpToken.of(
               userId.getValue(),
               this.getDecryptedSharedSecret(credBean),
               this.getOrganizationLabel(userId).orElse(null),
               this.getCredBeanData(credBean).get().getCounter(),
               this.oathOtpSettings().isTimeBased()
            )
         );
   }

   public boolean deleteToken(UserId userId) {
      boolean tokenExistedBeforeDeletion = this.hasToken(userId);
      CredentialBean cred = this.getCredentialBean(userId);
      cred.resetCredentialOrder();
      cred.setCurrent(null);
      cred.setNext(null);
      cred.deactivate();

      for (String contextPropertyToDelete : this.oathOtpTokenController.getDeletePropertiesWithCredential()) {
         cred.setValueToContextData(contextPropertyToDelete, null);
      }

      this.makeCredentialPersistent(userId, cred);
      this.signingLogger.log("Deleted OATH OTP token for user \"%s\"", new Object[]{userId});
      return tokenExistedBeforeDeletion;
   }

   public boolean hasToken(UserId userId) {
      return this.retrieveToken(userId).isPresent();
   }

   public boolean isAvailableForAdministration(UserId userId) {
      CredentialBean cred = this.getCredentialBean(userId);
      return cred != null && cred.isAvailableForAdministration();
   }

   public Optional<OathOtpActivationLetter> getActivationLetter(UserId userId) {
      CredentialData credentialData = this.getCredentialBean(userId).getCurrent();
      return credentialData != null && credentialData.getGenerationDate() != null
         ? Optional.of(OathOtpActivationLetter.builder().generationDate(credentialData.getGenerationDate()).build())
         : Optional.empty();
   }

   public OathOtpActivationLetterOrder getActivationLetterOrder(UserId userId) {
      CredentialBean credentialBean = this.getCredentialBean(userId);
      if (!this.hasToken(userId)) {
         throw new EntityNotFoundException("Cannot retrieve the activation letter order status. No OATH OTP token present.");
      }

      boolean ordered = credentialBean.getOrderCredentialFlag();
      return OathOtpActivationLetterOrder.builder().ordered(ordered).build();
   }

   public void orderActivationLetter(UserId userId) {
      CredentialBean credentialBean = this.getCredentialBean(userId);
      if (!this.hasToken(userId)) {
         throw new EntityNotFoundException("Cannot order an activation letter. No OATH OTP token present.");
      }

      credentialBean.orderNewCredential(this.administrator.getName());
      this.makeCredentialPersistent(userId, credentialBean);
      this.signingLogger.log("Ordered a new OATH OTP activation letter for user \"%s\".", new Object[]{userId.getValue()});
   }

   public void unorderActivationLetter(UserId userId) {
      CredentialBean credentialBean = this.getCredentialBean(userId);
      if (!this.hasToken(userId)) {
         throw new EntityNotFoundException("Cannot unorder an activation letter. No OATH OTP token present.");
      }

      credentialBean.resetCredentialOrder();
      this.makeCredentialPersistent(userId, credentialBean);
      this.signingLogger.log("Canceled OATH OTP activation letter for user \"%s\".", new Object[]{userId.getValue()});
   }

   public OathOtpTokenService.OathOtpTokenType getTokenType() {
      return this.oathOtpSettings().isTimeBased() ? OathOtpTokenService.OathOtpTokenType.TIME_BASED : OathOtpTokenService.OathOtpTokenType.EVENT_BASED;
   }

   public void updateChallenge(UserId userId, UnaryOperator<OtpCredBeanData> update) {
      this.commonOathOtpTokenService.updateChallenge(userId, update);
   }

   private void makeCredentialPersistent(UserId userId, CredentialBean credBean) {
      Preconditions.checkNotNull(credBean, "credential null");

      try {
         this.oathOtpSettings().getCredentialPersister().makeCredentialPersistent(userId.getValue(), credBean);
      } catch (PersisterException e) {
         throw new FailureException("Error storing credential for user \"" + userId.getValue() + "\": " + e.getMessage(), e);
      }
   }

   private boolean hasSecret(CredentialBean credBean) {
      return this.commonOathOtpTokenService.hasSecret(credBean);
   }

   private Optional<byte[]> getDecryptedSharedSecret(CredentialBean credentialBean) {
      return this.accessController.isActionAllowed(AdminappAction.VIEW_OATH_OTP_TOKEN_SECRET)
         ? this.commonOathOtpTokenService.getDecryptedSharedSecret(credentialBean)
         : Optional.empty();
   }

   private Optional<String> getOrganizationLabel(UserId userId) {
      return this.commonOathOtpTokenService.getOrganizationLabel(userId);
   }

   private Optional<OtpCredBeanData> getCredBeanData(CredentialBean credBean) {
      return this.commonOathOtpTokenService.getCredBeanData(credBean);
   }

   private OathOtpSettings oathOtpSettings() {
      return this.commonOathOtpTokenService.oathOtpSettings();
   }

   private CredentialBean getCredentialBean(UserId userId) {
      return this.commonOathOtpTokenService.getCredentialBean(userId);
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class AdminOathOtpTokenServiceImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private AdminappAccessController accessController;
      @SuppressFBWarnings(justification = "generated code")
      private SigningLogger signingLogger;
      @SuppressFBWarnings(justification = "generated code")
      private CurrentAdministrator administrator;
      @SuppressFBWarnings(justification = "generated code")
      private OathOtpTokenController oathOtpTokenController;
      @SuppressFBWarnings(justification = "generated code")
      private CommonOathOtpTokenServiceImpl commonOathOtpTokenService;

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

      @SuppressFBWarnings(justification = "generated code")
      public AdminOathOtpTokenServiceImpl.AdminOathOtpTokenServiceImplBuilder accessController(AdminappAccessController accessController) {
         this.accessController = accessController;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminOathOtpTokenServiceImpl.AdminOathOtpTokenServiceImplBuilder signingLogger(SigningLogger signingLogger) {
         this.signingLogger = signingLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminOathOtpTokenServiceImpl.AdminOathOtpTokenServiceImplBuilder administrator(CurrentAdministrator administrator) {
         this.administrator = administrator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminOathOtpTokenServiceImpl.AdminOathOtpTokenServiceImplBuilder oathOtpTokenController(OathOtpTokenController oathOtpTokenController) {
         this.oathOtpTokenController = oathOtpTokenController;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminOathOtpTokenServiceImpl.AdminOathOtpTokenServiceImplBuilder commonOathOtpTokenService(CommonOathOtpTokenServiceImpl commonOathOtpTokenService) {
         this.commonOathOtpTokenService = commonOathOtpTokenService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminOathOtpTokenServiceImpl build() {
         return new AdminOathOtpTokenServiceImpl(
            this.accessController, this.signingLogger, this.administrator, this.oathOtpTokenController, this.commonOathOtpTokenService
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "AdminOathOtpTokenServiceImpl.AdminOathOtpTokenServiceImplBuilder(accessController="
            + this.accessController
            + ", signingLogger="
            + this.signingLogger
            + ", administrator="
            + this.administrator
            + ", oathOtpTokenController="
            + this.oathOtpTokenController
            + ", commonOathOtpTokenService="
            + this.commonOathOtpTokenService
            + ")";
      }
   }
}
