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

import com.airlock.iam.authentication.api.domain.model.step.AuthenticationFlowStep;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResult;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultFactory;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.adapt.Adapter;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.user.CurrentLocale;
import com.airlock.iam.core.misc.authen.LogTypes;
import com.airlock.iam.core.misc.authen.credential.ChallengeResponseCredential;
import com.airlock.iam.core.misc.authen.result.AuthenticationFailed;
import com.airlock.iam.core.misc.authen.result.AuthenticationFailedChallenge;
import com.airlock.iam.core.misc.authen.result.AuthenticationResult;
import com.airlock.iam.core.misc.authen.result.MatrixChallengeCoordinate;
import com.airlock.iam.core.misc.impl.authen.credential.ChallengeResponseCredentialImpl;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public final class MatrixCheckingResultMapper {
   private static final ELogger LOG = ELoggerFactory.getLogger(MatrixCheckingResultMapper.class);
   static final String MATRIX_CHALLENGES_ADDITIONAL_ATTRIBUTE = "matrixChallenges";

   public static ChallengeResponseCredential mapResponsesToCredential(
      List<MatrixChallengeResponse> responses, List<String> expectedCoordinates, CurrentLocale locale
   ) {
      String challengeResponseForAuthenticator = expectedCoordinates.stream()
         .map(coord -> getResponseForChallenge(responses, coord))
         .flatMap(Optional::stream)
         .collect(Collectors.joining(" "));
      ChallengeResponseCredentialImpl res = new ChallengeResponseCredentialImpl(challengeResponseForAuthenticator);
      res.setDisplayLanguage(locale.getLanguage());
      return res;
   }

   private static Optional<String> getResponseForChallenge(List<MatrixChallengeResponse> responses, String challenge) {
      Optional<String> response = responses.stream().filter(r -> challenge.equals(r.getChallenge())).map(MatrixChallengeResponse::getResponse).findFirst();
      if (response.isEmpty()) {
         LOG.warn(LogTypes.AUTHENTICATION_FAILED_SAT, "Matrix response did not contain challenge " + LogUtil.quote(challenge));
      }

      return response;
   }

   private static AuthenticationFailedChallenge toFailedChallenge(AuthenticationResult ar) {
      return Adapter.adapt(ar).to(AuthenticationFailedChallenge.class);
   }

   public static AuthenticationStepResult mapResultAndUpdateSession(
      AuthenticationResult authResult, MatrixCheckingStepSession matrixCheckingStepSession, AuthenticationStepResultFactory stepResultFactory
   ) {
      updateChallengeInSession(authResult, matrixCheckingStepSession);
      if (authResult.authenticationSucceeded()) {
         return stepResultFactory.success().build();
      } else if (authResult.isOfType(AuthenticationFailedChallenge.INDEX_CHALLENGE) || authResult.isOfType(AuthenticationFailedChallenge.MATRIX_CHALLENGE)) {
         return stepResultFactory.stay(MatrixCheckingStepActions.MATRIX_RESPONSE_REQUIRED)
            .addAdditionalAttribute("matrixChallenges", matrixChallengesAttribute(matrixCheckingStepSession))
            .build();
      } else if (authResult.isOfType(AuthenticationFailedChallenge.RESPONSE_WRONG_INDEX_CHALLENGE)
         || authResult.isOfType(AuthenticationFailedChallenge.RESPONSE_WRONG_MATRIX_CHALLENGE)) {
         return stepResultFactory.failRetry(MatrixCheckingStep.MatrixAuthStepErrors.MATRIX_RESPONSE_WRONG, MatrixCheckingStepActions.MATRIX_RESPONSE_REQUIRED)
            .addAdditionalAttribute("matrixChallenges", matrixChallengesAttribute(matrixCheckingStepSession))
            .build();
      } else if (authResult.isOfType(AuthenticationFailedChallenge.RESPONSE_WRONG) || authResult.isOfType(AuthenticationFailedChallenge.RESPONSE_EXPIRED)) {
         return stepResultFactory.fail(MatrixCheckingStep.MatrixAuthStepErrors.MATRIX_RESPONSE_WRONG).build();
      } else if (authResult.isOfType(AuthenticationFailed.CREDENTIAL_NOT_ASSIGNED)) {
         return stepResultFactory.fail(AuthenticationFlowStep.GeneralAuthenticationStepError.NO_VALID_TOKEN).build();
      } else if (authResult.isOfType(AuthenticationFailed.TOO_MANY_UNANSWERED_CHALLENGES)) {
         return stepResultFactory.fail(MatrixCheckingStep.MatrixAuthStepErrors.TOO_MANY_UNANSWERED_CHALLENGES).build();
      } else {
         throw new FailureException("Unmappable authentication result: " + authResult);
      }
   }

   private static List<String> matrixChallengesAttribute(MatrixCheckingStepSession matrixCheckingStepSession) {
      return matrixCheckingStepSession.getCurrentChallenge().map(MatrixChallenge::getChallenges).orElseThrow();
   }

   private static void updateChallengeInSession(AuthenticationResult authResult, MatrixCheckingStepSession matrixCheckingStepSession) {
      if (authResult.isOfType(AuthenticationFailedChallenge.INDEX_CHALLENGE)
         || authResult.isOfType(AuthenticationFailedChallenge.RESPONSE_WRONG_INDEX_CHALLENGE)) {
         MatrixChallenge.MatrixChallengeBuilder challengeBuilder = MatrixChallenge.builder();
         challengeBuilder.listId(AuthenticationFailedChallenge.INDEX_CHALLENGE.getTokenIdentifier(toFailedChallenge(authResult)));

         for (int i : AuthenticationFailedChallenge.INDEX_CHALLENGE.getIndices(toFailedChallenge(authResult))) {
            challengeBuilder.challenge(Integer.toString(i));
         }

         matrixCheckingStepSession.updateChallenge(challengeBuilder.build());
      } else if (!authResult.isOfType(AuthenticationFailedChallenge.MATRIX_CHALLENGE)
         && !authResult.isOfType(AuthenticationFailedChallenge.RESPONSE_WRONG_MATRIX_CHALLENGE)) {
         matrixCheckingStepSession.clearChallenge();
      } else {
         MatrixChallenge.MatrixChallengeBuilder challengeBuilder = MatrixChallenge.builder();
         challengeBuilder.listId(AuthenticationFailedChallenge.MATRIX_CHALLENGE.getTokenIdentifier(toFailedChallenge(authResult)));

         for (MatrixChallengeCoordinate i : AuthenticationFailedChallenge.MATRIX_CHALLENGE.getCoordinates(toFailedChallenge(authResult))) {
            challengeBuilder.challenge(i.getRowIdentifier() + i.getColumnIdentifier());
         }

         matrixCheckingStepSession.updateChallenge(challengeBuilder.build());
      }
   }

   private MatrixCheckingResultMapper() {
   }
}
