package com.airlock.iam.transactionapproval.domain.model.cronto;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.message.MessageFormattingException;
import com.airlock.iam.base.misc.message.MessageTemplate;
import com.airlock.iam.common.api.domain.model.cronto.CrontoMessageProvider;
import com.airlock.iam.common.api.domain.model.cronto.CrontoPushInformation;
import com.airlock.iam.common.api.domain.model.cronto.CrontoTransactionInformation;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.core.misc.cronto.CrontoChallenge;
import com.airlock.iam.core.misc.cronto.CrontoHandler;
import com.airlock.iam.core.misc.impl.cronto.CrontoChallengeResult.CrontoMethod;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.transactionapproval.application.configuration.cronto.CrontoMessageProviderImplConfig;
import com.airlock.iam.transactionapproval.domain.model.message.MessageDataProvider;
import com.airlock.iam.transactionapproval.domain.model.message.MessageProviderStringResolver;
import com.airlock.iam.transactionapproval.domain.model.message.MessageTranslationKey;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;

@LogicForConfig(CrontoMessageProviderImplConfig.class)
public class CrontoMessageProviderImpl implements CrontoMessageProvider {
   private static final ELogger LOG = ELoggerFactory.getLogger(CrontoMessageProviderImpl.class);
   private static final int MAX_MESSAGE_SIZE = 130;
   private static final int MIN_MESSAGE_SIZE = 1;
   private final Provider<PrincipalInfo> user;
   private final CrontoHandler crontoHandler;
   private final CrontoMessageProviderKeys keys;
   private final MessageProviderStringResolver stringResolver;
   private final MessageDataProvider valueProvider;
   private final MessageTranslationKey translationKeys;
   private final CrontoMessageProviderImpl.PushTranslationKeys pushTranslationKeys;

   @Inject
   public CrontoMessageProviderImpl(
      Provider<PrincipalInfo> user,
      CrontoHandler crontoHandler,
      CrontoMessageProviderKeys keys,
      MessageProviderStringResolver stringResolver,
      MessageDataProvider valueProvider,
      MessageTranslationKey translationKeys,
      CrontoMessageProviderImpl.PushTranslationKeys pushTranslationKeys
   ) {
      this.user = user;
      this.crontoHandler = crontoHandler;
      this.keys = keys;
      this.stringResolver = stringResolver;
      this.valueProvider = valueProvider;
      this.translationKeys = translationKeys;
      this.pushTranslationKeys = pushTranslationKeys;
   }

   public CrontoTransactionInformation generateCrontoMessage(Optional<String> pushLoginId) {
      UserId userId = ((UserIdentity)((PrincipalInfo)this.user.get()).getKnownIdentity().orElseThrow()).id();
      String amountLabelValue = this.stringResolver.getStringResourceForKey(this.keys.getAmountLabelKey()).orElse(null);
      String amountValue = this.resolveParameterName(this.keys.getAmountParameterName());
      String currencyValue = this.resolveParameterName(this.keys.getCurrencyParameterName());
      MessageTemplate messageTemplate = this.stringResolver.getMessageTemplateInGrowableFormat(this.translationKeys.messageTranslationKey());
      int maxSize = 130;
      CrontoTransactionInformation enc = this.createEncodedMessage(amountLabelValue, amountValue, currencyValue, messageTemplate, maxSize);
      long startTime = System.currentTimeMillis();

      while (!this.crontoMessageIsCreatable(userId, enc) && maxSize > 1) {
         enc = this.createEncodedMessage(amountLabelValue, amountValue, currencyValue, messageTemplate, --maxSize);
      }

      long endTime = System.currentTimeMillis();
      LOG.debug("Cronto message size=" + maxSize + ", calculation took " + (endTime - startTime) + " ms");
      return enc;
   }

   public CrontoPushInformation generateCrontoPushInformation(Optional<String> pushLoginId) {
      return new CrontoPushInformation(
         this.translate(this.pushTranslationKeys.pushTitleTranslationKey), this.translate(this.pushTranslationKeys.pushSubjectTranslationKey)
      );
   }

   private String resolveParameterName(@Nullable String parameterName) {
      return parameterName == null ? null : this.valueProvider.getValueForKey(parameterName).map(String.class::cast).orElse(null);
   }

   @SuppressFBWarnings("REC_CATCH_EXCEPTION")
   private boolean crontoMessageIsCreatable(UserId userId, CrontoTransactionInformation message) {
      try {
         CrontoChallenge challenge = this.crontoHandler
            .createImageChallenge(userId, Optional.of(CrontoMethod.SCAN_TAN), message, new CrontoPushInformation("any", "any"))
            .getChallenge();
         challenge.getCryptogramForScreen();
         return true;
      } catch (Exception e) {
         return false;
      }
   }

   private CrontoTransactionInformation createEncodedMessage(
      String amountLabelValue, String amountValue, String currencyValue, MessageTemplate messageTemplate, int maxSize
   ) {
      return messageTemplate.formatGrowableMessage(maxSize)
         .map(details -> this.encodeValues(amountLabelValue, amountValue, currencyValue, details))
         .orElseThrow(() -> new MessageFormattingException("Not possible to create transaction approval message for Cronto."));
   }

   private CrontoTransactionInformation encodeValues(String amountLabelValue, String amountValue, String currencyValue, String details) {
      return CrontoTransactionInformation.builder()
         .details(details)
         .amount(Optional.ofNullable(amountValue))
         .currency(Optional.ofNullable(currencyValue))
         .amountLabel(Optional.ofNullable(amountLabelValue))
         .build();
   }

   private String translate(String resourceKey) {
      return (String)this.stringResolver
         .getMessageTemplateInGrowableFormat(resourceKey)
         .formatGrowableMessage(Integer.MAX_VALUE)
         .orElseThrow(() -> new FailureException("Unable to format Message with resource key " + resourceKey));
   }

   public static final class PushTranslationKeys {
      private final String pushTitleTranslationKey;
      private final String pushSubjectTranslationKey;

      @SuppressFBWarnings(justification = "generated code")
      PushTranslationKeys(String pushTitleTranslationKey, String pushSubjectTranslationKey) {
         this.pushTitleTranslationKey = pushTitleTranslationKey;
         this.pushSubjectTranslationKey = pushSubjectTranslationKey;
      }

      @SuppressFBWarnings(justification = "generated code")
      public static CrontoMessageProviderImpl.PushTranslationKeys.PushTranslationKeysBuilder builder() {
         return new CrontoMessageProviderImpl.PushTranslationKeys.PushTranslationKeysBuilder();
      }

      @SuppressFBWarnings(justification = "generated code")
      public String getPushTitleTranslationKey() {
         return this.pushTitleTranslationKey;
      }

      @SuppressFBWarnings(justification = "generated code")
      public String getPushSubjectTranslationKey() {
         return this.pushSubjectTranslationKey;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof CrontoMessageProviderImpl.PushTranslationKeys other)) {
            return false;
         } else {
            Object this$pushTitleTranslationKey = this.pushTitleTranslationKey;
            Object other$pushTitleTranslationKey = other.pushTitleTranslationKey;
            if (this$pushTitleTranslationKey == null
               ? other$pushTitleTranslationKey == null
               : this$pushTitleTranslationKey.equals(other$pushTitleTranslationKey)) {
               Object this$pushSubjectTranslationKey = this.pushSubjectTranslationKey;
               Object other$pushSubjectTranslationKey = other.pushSubjectTranslationKey;
               return this$pushSubjectTranslationKey == null
                  ? other$pushSubjectTranslationKey == null
                  : this$pushSubjectTranslationKey.equals(other$pushSubjectTranslationKey);
            } else {
               return false;
            }
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         Object $pushTitleTranslationKey = this.pushTitleTranslationKey;
         result = result * 59 + ($pushTitleTranslationKey == null ? 43 : $pushTitleTranslationKey.hashCode());
         Object $pushSubjectTranslationKey = this.pushSubjectTranslationKey;
         return result * 59 + ($pushSubjectTranslationKey == null ? 43 : $pushSubjectTranslationKey.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "CrontoMessageProviderImpl.PushTranslationKeys(pushTitleTranslationKey="
            + this.getPushTitleTranslationKey()
            + ", pushSubjectTranslationKey="
            + this.getPushSubjectTranslationKey()
            + ")";
      }

      @SuppressFBWarnings(justification = "generated code")
      public static class PushTranslationKeysBuilder {
         @SuppressFBWarnings(justification = "generated code")
         private String pushTitleTranslationKey;
         @SuppressFBWarnings(justification = "generated code")
         private String pushSubjectTranslationKey;

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

         @SuppressFBWarnings(justification = "generated code")
         public CrontoMessageProviderImpl.PushTranslationKeys.PushTranslationKeysBuilder pushTitleTranslationKey(String pushTitleTranslationKey) {
            this.pushTitleTranslationKey = pushTitleTranslationKey;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public CrontoMessageProviderImpl.PushTranslationKeys.PushTranslationKeysBuilder pushSubjectTranslationKey(String pushSubjectTranslationKey) {
            this.pushSubjectTranslationKey = pushSubjectTranslationKey;
            return this;
         }

         @SuppressFBWarnings(justification = "generated code")
         public CrontoMessageProviderImpl.PushTranslationKeys build() {
            return new CrontoMessageProviderImpl.PushTranslationKeys(this.pushTitleTranslationKey, this.pushSubjectTranslationKey);
         }

         @SuppressFBWarnings(justification = "generated code")
         @Override
         public String toString() {
            return "CrontoMessageProviderImpl.PushTranslationKeys.PushTranslationKeysBuilder(pushTitleTranslationKey="
               + this.pushTitleTranslationKey
               + ", pushSubjectTranslationKey="
               + this.pushSubjectTranslationKey
               + ")";
         }
      }
   }
}
