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

import com.airlock.iam.admin.api.domain.model.token.generic.ActivationLetterOrderStatus;
import com.airlock.iam.admin.api.domain.model.token.generic.GenericToken;
import com.airlock.iam.admin.api.domain.model.token.generic.GenericTokenAttributeMapping;
import com.airlock.iam.admin.api.domain.model.token.generic.GenericTokenRepository;
import com.airlock.iam.admin.api.domain.service.user.ManagedUserRepository;
import com.airlock.iam.admin.domain.model.authmethod.AuthenticationMethod;
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.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.api.domain.model.validation.Validator;
import com.airlock.iam.log.api.signing.SigningLogger;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class GenericTokenService {
   private final GenericTokenRepository tokenRepository;
   private final GenericTokenAttributeMapping attributeMapping;
   private final ManagedUserRepository userRepository;
   private final SigningLogger signingLogger;
   private final Validator<Map<String, Object>> attributeValidator;
   private final GenericTokenEventService eventService;
   private final AuthenticationMethod.TokenType tokenType;

   public Optional<GenericToken> findToken(UserId userId, String tokenId) {
      return this.tokenRepository.findToken(tokenId, userId).map(this::mapToExternal);
   }

   public List<GenericToken> getTokens(UserId userId) {
      return this.tokenRepository.getTokens(userId).stream().map(this::mapToExternal).collect(Collectors.toList());
   }

   public void deleteToken(UserId userId, String tokenId) {
      Optional<GenericToken> existingToken = this.tokenRepository.findToken(tokenId, userId);
      this.tokenRepository.deleteToken(tokenId, userId);
      existingToken.ifPresent(token -> this.eventService.publishTokenDeleted(userId, token));
      this.signingLogger.log("Removed generic token %s of type %s.", new Object[]{tokenId, this.tokenType});
   }

   public void enableToken(UserId userId, String tokenId) {
      this.tokenRepository.enableToken(tokenId, userId);
      this.signingLogger.log("Enabled generic token %s of type %s.", new Object[]{tokenId, this.tokenType});
   }

   public void disableToken(UserId userId, String tokenId) {
      this.tokenRepository.disableToken(tokenId, userId);
      this.signingLogger.log("Disabled generic token %s of type %s.", new Object[]{tokenId, this.tokenType});
   }

   public GenericToken addToken(UserId userId, Map<String, Object> values) {
      ValidationResult result = this.validateAttributes(values);
      if (!result.isValid()) {
         throw new FailureException("Attributes are not valid.");
      }

      GenericToken token = this.mapToInternal(values, this.tokenRepository.newToken());
      token = this.tokenRepository.addToken(token, userId);
      this.eventService.publishTokenAdded(userId, token);
      this.signingLogger.log("Added generic token %s of type %s.", new Object[]{token.getTokenId(), this.tokenType});
      return this.mapToExternal(token);
   }

   public ValidationResult validateAttributes(Map<String, Object> attributes) {
      return this.attributeValidator.validate(attributes);
   }

   public ActivationLetterOrderStatus getActivationLetterOrderStatus(UserId userId) {
      return this.tokenRepository.getActivationLetterOrderStatus(userId);
   }

   public void orderNewActivationLetter(UserId userId) {
      if (this.tokenRepository.getActivationLetterOrderStatus(userId).isOrdered()) {
         throw new ViolationException(String.format("Activation letter for %s and %s already ordered.", this.tokenType, userId));
      }

      this.tokenRepository.orderNewActivationLetter(userId);
      this.signingLogger.log("Ordered activation letter for generic tokens of type %s.", new Object[]{this.tokenType});
   }

   public void cancelActivationLetterOrder(UserId userId) {
      if (!this.tokenRepository.getActivationLetterOrderStatus(userId).isOrdered()) {
         throw new ViolationException(String.format("No activation letter for %s and %s is ordered.", this.tokenType, userId));
      }

      this.tokenRepository.cancelActivationLetterOrder(userId);
      this.signingLogger.log("Cancelled order of activation letter for generic tokens of type %s.", new Object[]{this.tokenType});
   }

   public GenericToken updateToken(UserId userId, String tokenId, Map<String, Object> changedAttributes) {
      ValidationResult result = this.validateAttributes(changedAttributes);
      if (!result.isValid()) {
         throw new FailureException("Attributes are not valid.");
      }

      GenericToken original = this.tokenRepository.getToken(tokenId, userId);
      GenericToken updated = this.mapToInternal(changedAttributes, original);
      updated = this.tokenRepository.updateToken(updated, userId);
      this.eventService.publishTokenChanged(userId, original, updated);
      this.signingLogger.log("Updated generic token %s of type %s.", new Object[]{tokenId, this.tokenType});
      return this.mapToExternal(updated);
   }

   private GenericToken mapToExternal(GenericToken token) {
      Map<String, Object> attributes = this.attributeMapping.getMappedAttributes(token);
      return token.toBuilder().mappedAttributes(attributes).build();
   }

   private GenericToken mapToInternal(Map<String, Object> values, GenericToken token) {
      GenericToken copy = token.copy();
      this.attributeMapping.addMappedAttributes(values, copy);
      return copy;
   }

   @SuppressFBWarnings(justification = "generated code")
   GenericTokenService(
      GenericTokenRepository tokenRepository,
      GenericTokenAttributeMapping attributeMapping,
      ManagedUserRepository userRepository,
      SigningLogger signingLogger,
      Validator<Map<String, Object>> attributeValidator,
      GenericTokenEventService eventService,
      AuthenticationMethod.TokenType tokenType
   ) {
      this.tokenRepository = tokenRepository;
      this.attributeMapping = attributeMapping;
      this.userRepository = userRepository;
      this.signingLogger = signingLogger;
      this.attributeValidator = attributeValidator;
      this.eventService = eventService;
      this.tokenType = tokenType;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class GenericTokenServiceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private GenericTokenRepository tokenRepository;
      @SuppressFBWarnings(justification = "generated code")
      private GenericTokenAttributeMapping attributeMapping;
      @SuppressFBWarnings(justification = "generated code")
      private ManagedUserRepository userRepository;
      @SuppressFBWarnings(justification = "generated code")
      private SigningLogger signingLogger;
      @SuppressFBWarnings(justification = "generated code")
      private Validator<Map<String, Object>> attributeValidator;
      @SuppressFBWarnings(justification = "generated code")
      private GenericTokenEventService eventService;
      @SuppressFBWarnings(justification = "generated code")
      private AuthenticationMethod.TokenType tokenType;

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

      @SuppressFBWarnings(justification = "generated code")
      public GenericTokenService.GenericTokenServiceBuilder tokenRepository(GenericTokenRepository tokenRepository) {
         this.tokenRepository = tokenRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public GenericTokenService.GenericTokenServiceBuilder attributeMapping(GenericTokenAttributeMapping attributeMapping) {
         this.attributeMapping = attributeMapping;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public GenericTokenService.GenericTokenServiceBuilder userRepository(ManagedUserRepository userRepository) {
         this.userRepository = userRepository;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public GenericTokenService.GenericTokenServiceBuilder attributeValidator(Validator<Map<String, Object>> attributeValidator) {
         this.attributeValidator = attributeValidator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public GenericTokenService.GenericTokenServiceBuilder eventService(GenericTokenEventService eventService) {
         this.eventService = eventService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public GenericTokenService.GenericTokenServiceBuilder tokenType(AuthenticationMethod.TokenType tokenType) {
         this.tokenType = tokenType;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public GenericTokenService build() {
         return new GenericTokenService(
            this.tokenRepository, this.attributeMapping, this.userRepository, this.signingLogger, this.attributeValidator, this.eventService, this.tokenType
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "GenericTokenService.GenericTokenServiceBuilder(tokenRepository="
            + this.tokenRepository
            + ", attributeMapping="
            + this.attributeMapping
            + ", userRepository="
            + this.userRepository
            + ", signingLogger="
            + this.signingLogger
            + ", attributeValidator="
            + this.attributeValidator
            + ", eventService="
            + this.eventService
            + ", tokenType="
            + this.tokenType
            + ")";
      }
   }
}
