package com.airlock.iam.flow.shared.domain.model.mtan;

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.api.domain.model.mapification.MapifiedValues;
import com.airlock.iam.base.domain.model.mapification.MapificationUtils;
import com.airlock.iam.base.domain.model.mapification.MapifiedValuesBuilder;
import com.airlock.iam.common.api.domain.model.otp.OneTimePassword;
import com.airlock.iam.common.domain.model.otp.OtpMapifier;
import com.airlock.iam.core.misc.authen.result.AuthenticationFailed;
import com.airlock.iam.flow.api.domain.model.step.StepSession;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.Strings;

public final class MtanOtpCheckingSession implements StepSession {
   private static final String OTP_KEY = "otp";
   private static final String ATTEMPTS_KEY = "attempts";
   private static final String MAX_ATTEMPTS_KEY = "maxAttempts";
   private static final String CHOICES_KEY = "choices";
   private static final String SELECTED_KEY = "selected";
   private static final String CHOICE_ID_KEY = "id";
   private static final String CHOICE_PRESELECTED_KEY = "preselected";
   private static final String CHOICE_LABEL_KEY = "label";
   private static final String CHOICE_NUMBER_KEY = "number";
   private static final String RESENDS_KEY = "resends";
   private static final String MAX_RESENDS_KEY = "maxResends";
   private Optional<OneTimePassword> otp = Optional.empty();
   private int attempts;
   private final int maxAttempts;
   private List<AuthenticationFailed.MtanChoiceToken> mtanChoices = Collections.emptyList();
   private Optional<AuthenticationFailed.MtanChoiceToken> selectedChoice = Optional.empty();
   private int retransmissions;
   private final int maxRetransmissions;

   private MtanOtpCheckingSession(Integer maxAttempts, @Nullable Integer maxRetransmissions) {
      this.maxAttempts = ViolationException.checkNotNull(maxAttempts);
      this.maxRetransmissions = maxRetransmissions != null ? maxRetransmissions : Integer.MAX_VALUE;
   }

   public void setOtp(OneTimePassword otp) {
      ViolationException.checkFalse(this.hasOtp(), "Cannot create another OTP when an OTP already exists");
      this.otp = Optional.of(otp);
   }

   public OneTimePassword getOtp() {
      return this.otp.orElseThrow();
   }

   public void clearOtp() {
      this.otp = Optional.empty();
   }

   public boolean hasOtp() {
      return this.otp.isPresent();
   }

   public boolean maxAttemptsExceeded() {
      return this.attempts >= this.maxAttempts;
   }

   public void increaseAttempts() {
      this.attempts++;
   }

   public boolean maxRetransmissionsExceeded() {
      return this.retransmissions >= this.maxRetransmissions;
   }

   public void increaseRetransmissions() {
      this.retransmissions++;
   }

   public Optional<AuthenticationFailed.MtanChoiceToken> getMtanChoiceById(String id) {
      return this.mtanChoices.stream().filter(choice -> Strings.CS.equals(id, choice.getId())).findFirst();
   }

   public void selectMtanChoice(AuthenticationFailed.MtanChoiceToken choice) {
      this.selectedChoice = Optional.of(choice);
   }

   public void destroy() {
      this.clearOtp();
      this.attempts = 0;
      this.retransmissions = 0;
      this.mtanChoices = Collections.emptyList();
      this.selectedChoice = Optional.empty();
   }

   @Override
   public MapifiedValues mapify() {
      return MapifiedValuesBuilder.mapifiedValues()
         .putIfPresent("otp", this.otp.map(OtpMapifier::mapifyOtp))
         .put("attempts", this.attempts)
         .put("maxAttempts", this.maxAttempts)
         .putIfNotEmpty("choices", MapificationUtils.mapifyCollection(this.mtanChoices, MtanOtpCheckingSession::mapifyChoiceToken))
         .putIfPresent("selected", this.selectedChoice.map(MtanOtpCheckingSession::mapifyChoiceToken))
         .put("resends", this.retransmissions)
         .put("maxResends", this.maxRetransmissions)
         .build();
   }

   public static MtanOtpCheckingSession demapify(MapifiedValues values) {
      return new MtanOtpCheckingSession(
         values.demapifyOptional("otp", OtpMapifier::demapifyOtp),
         values.<Integer>get("attempts"),
         values.<Integer>get("maxAttempts"),
         values.demapifyList("choices", MtanOtpCheckingSession::demapifyChoiceToken),
         values.demapifyOptional("selected", MtanOtpCheckingSession::demapifyChoiceToken),
         values.<Integer>get("resends"),
         values.<Integer>get("maxResends")
      );
   }

   private static MapifiedValues mapifyChoiceToken(AuthenticationFailed.MtanChoiceToken choice) {
      return MapifiedValuesBuilder.mapifiedValues()
         .put("id", choice.getId())
         .put("preselected", choice.isPreSelected())
         .putIfPresent("label", choice.getLabel())
         .put("number", choice.getMaskedPhoneNumber())
         .build();
   }

   private static AuthenticationFailed.MtanChoiceToken demapifyChoiceToken(MapifiedValues values) {
      return new AuthenticationFailed.MtanChoiceToken(
         values.get("id"), values.<Boolean>get("preselected"), values.get("number"), values.<String>getOptional("label").orElse(null)
      );
   }

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

   @SuppressFBWarnings(justification = "generated code")
   private MtanOtpCheckingSession(
      Optional<OneTimePassword> otp,
      int attempts,
      int maxAttempts,
      List<AuthenticationFailed.MtanChoiceToken> mtanChoices,
      Optional<AuthenticationFailed.MtanChoiceToken> selectedChoice,
      int retransmissions,
      int maxRetransmissions
   ) {
      this.otp = otp;
      this.attempts = attempts;
      this.maxAttempts = maxAttempts;
      this.mtanChoices = mtanChoices;
      this.selectedChoice = selectedChoice;
      this.retransmissions = retransmissions;
      this.maxRetransmissions = maxRetransmissions;
   }

   @SuppressFBWarnings(justification = "generated code")
   public int getAttempts() {
      return this.attempts;
   }

   @SuppressFBWarnings(justification = "generated code")
   public List<AuthenticationFailed.MtanChoiceToken> getMtanChoices() {
      return this.mtanChoices;
   }

   @SuppressFBWarnings(justification = "generated code")
   public void setMtanChoices(List<AuthenticationFailed.MtanChoiceToken> mtanChoices) {
      this.mtanChoices = mtanChoices;
   }

   @SuppressFBWarnings(justification = "generated code")
   public Optional<AuthenticationFailed.MtanChoiceToken> getSelectedChoice() {
      return this.selectedChoice;
   }

   @SuppressFBWarnings(justification = "generated code")
   public int getRetransmissions() {
      return this.retransmissions;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class MtanOtpCheckingSessionBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Integer maxAttempts;
      @SuppressFBWarnings(justification = "generated code")
      private Integer maxRetransmissions;

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

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckingSession.MtanOtpCheckingSessionBuilder maxAttempts(Integer maxAttempts) {
         this.maxAttempts = maxAttempts;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckingSession.MtanOtpCheckingSessionBuilder maxRetransmissions(@Nullable Integer maxRetransmissions) {
         this.maxRetransmissions = maxRetransmissions;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public MtanOtpCheckingSession build() {
         return new MtanOtpCheckingSession(this.maxAttempts, this.maxRetransmissions);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "MtanOtpCheckingSession.MtanOtpCheckingSessionBuilder(maxAttempts="
            + this.maxAttempts
            + ", maxRetransmissions="
            + this.maxRetransmissions
            + ")";
      }
   }
}
