package com.airlock.iam.authentication.domain.model.migration;

import com.airlock.iam.authentication.application.configuration.migration.MigrationSelectionStepConfig;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.common.api.domain.model.user.PersistentUser;
import com.airlock.iam.common.domain.model.principal.UserPrincipal;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.common.domain.model.user.UserIdentityLogs;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.selection.Option;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.api.domain.model.step.StepResultBuilder;
import com.airlock.iam.flow.api.domain.model.step.StepResultData;
import com.airlock.iam.flow.api.domain.model.step.StepSession;
import com.airlock.iam.log.api.signing.SigningLogger;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@LogicForConfig(MigrationSelectionStepConfig.class)
public final class MigrationSelectionStepImpl implements MigrationSelectionStep {
   private static final ELogger LOG = ELoggerFactory.getLogger(MigrationSelectionStepImpl.class);
   private static final String MIGRATION_INFO = "migrationInfo";
   private static final Option SKIP_MIGRATION_OPTION = Option.of("SKIP_MIGRATION");
   private static final Option NEVER_MIGRATE_OPTION = Option.of("NEVER_MIGRATE");
   private static final String OPTIONS_ATTRIBUTE = "options";
   private static final String SKIP_POSSIBLE_ATTRIBUTE = "skipPossible";
   private static final String REJECT_POSSIBLE_ATTRIBUTE = "rejectPossible";
   private static final String NOT_AFTER_ATTRIBUTE = "notAfter";
   private final Provider<MigrationSelectionStepSession> stepSession;
   private final Provider<SigningLogger> signingLogger;
   private final Provider<UserPrincipal> userPrincipal;
   private final Provider<PrincipalInfo> principalInfo;
   private final Provider<List<SelectableMigrationOption>> migrationOptions;

   @Inject
   public MigrationSelectionStepImpl(
      Provider<MigrationSelectionStepSession> stepSession,
      Provider<SigningLogger> signingLogger,
      Provider<UserPrincipal> userPrincipal,
      Provider<PrincipalInfo> principalInfo,
      Provider<List<SelectableMigrationOption>> migrationOptions
   ) {
      this.stepSession = stepSession;
      this.signingLogger = signingLogger;
      this.userPrincipal = userPrincipal;
      this.principalInfo = principalInfo;
      this.migrationOptions = migrationOptions;
   }

   @Override
   public StepResult initialize() {
      List<Option> options = this.determineMigrationOptions();
      if (options.isEmpty()) {
         LOG.info("No migration subflow is selectable - skip migration");
         return this.stepResultFactory().skip().build();
      } else {
         this.initializeSessionValues(options);
         return this.addInfo(this.stepResultFactory().stay(MigrationSelectionStep.MigrationStepActions.MIGRATION_SELECTION_REQUIRED)).build();
      }
   }

   @Override
   public Optional<StepSession> createSession() {
      return Optional.of(new MigrationSelectionStepSession());
   }

   @Override
   public String getDescription() {
      return "Migration Selection Step";
   }

   @Override
   public StepResult retrieveOptions() {
      Set<Option> options = ((MigrationSelectionStepSession)this.stepSession.get()).getMigrationOptions();
      MigrationInfo migrationInfo = this.createMigrationInfo();
      return this.stepResultFactory()
         .stay(MigrationSelectionStep.MigrationStepActions.MIGRATION_SELECTION_REQUIRED)
         .resultData(resultDataFromOptions(options))
         .addMetadataItem("migrationInfo", migrationInfo)
         .build();
   }

   @Override
   public StepResult select(Option option) {
      Set<Option> options = ((MigrationSelectionStepSession)this.stepSession.get()).getMigrationOptions();
      if (options.isEmpty()) {
         return this.stepResultFactory().failDueToUnexpectedCall("no migration options available for selection").build();
      } else if (!options.contains(option)) {
         LOG.debug(String.format("Failed to select migration subflow based on user choice because flow '%s' is not available.", option));
         return this.addInfo(
               this.stepResultFactory()
                  .failRetry(MigrationSelectionStep.MigrationError.SELECTION_FAILED, MigrationSelectionStep.MigrationStepActions.MIGRATION_SELECTION_REQUIRED)
            )
            .build();
      } else {
         ((SigningLogger)this.signingLogger.get())
            .log(
               "User '%s' selected migration option '%s'.",
               new Object[]{UserIdentityLogs.orElseNotAvailable(((PrincipalInfo)this.principalInfo.get()).getKnownIdentity()), option}
            );
         return this.stepResultFactory().selectSubflow(option).build();
      }
   }

   @Override
   public StepResult skip() {
      if (!((MigrationSelectionStepSession)this.stepSession.get()).isSkipPossible()) {
         LOG.debug(
            String.format(
               "Skipping migration is not possible for user %s",
               UserIdentityLogs.orElseNotAvailable(((PrincipalInfo)this.principalInfo.get()).getKnownIdentity())
            )
         );
         return this.stepResultFactory().failDueToUnexpectedCall("Skipping migration is not possible").build();
      } else {
         ((SigningLogger)this.signingLogger.get())
            .log(
               "User '%s' skipped migration.", new Object[]{UserIdentityLogs.orElseNotAvailable(((PrincipalInfo)this.principalInfo.get()).getKnownIdentity())}
            );
         return this.stepResultFactory().selectSubflow(SKIP_MIGRATION_OPTION).build();
      }
   }

   @Override
   public StepResult reject() {
      if (!((MigrationSelectionStepSession)this.stepSession.get()).isRejectPossible()) {
         LOG.debug(
            String.format(
               "Rejecting migration is not possible for user %s",
               UserIdentityLogs.orElseNotAvailable(((PrincipalInfo)this.principalInfo.get()).getKnownIdentity())
            )
         );
         return this.stepResultFactory().failDueToUnexpectedCall("Rejecting migration is not possible").build();
      } else {
         return this.stepResultFactory().selectSubflow(NEVER_MIGRATE_OPTION).build();
      }
   }

   private void initializeSessionValues(List<Option> options) {
      ((MigrationSelectionStepSession)this.stepSession.get()).setOptions(options);
      ((MigrationSelectionStepSession)this.stepSession.get()).setRejectPossible(this.isRejectPossible());
      ((MigrationSelectionStepSession)this.stepSession.get()).setSkipPossible(this.isSkipPossible());
   }

   private static StepResultData resultDataFromOptions(Set<Option> options) {
      return new MigrationSelectionOptions(options.stream().map(Option::getName).collect(Collectors.toSet()));
   }

   private MigrationInfo createMigrationInfo() {
      PersistentUser user = ((UserPrincipal)this.userPrincipal.get()).user();
      boolean rejectPossible = ((MigrationSelectionStepSession)this.stepSession.get()).isRejectPossible();
      boolean skipPossible = ((MigrationSelectionStepSession)this.stepSession.get()).isSkipPossible();
      return new MigrationInfo(user.getAuthMigrationDate(), rejectPossible, skipPossible);
   }

   private StepResultBuilder addInfo(StepResultBuilder builder) {
      MigrationInfo info = this.createMigrationInfo();
      List<String> options = ((MigrationSelectionStepSession)this.stepSession.get()).getMigrationOptions().stream().map(Option::getName).toList();
      Map<String, Object> map = new HashMap<>();
      map.put("rejectPossible", info.isRejectPossible());
      map.put("skipPossible", info.isSkipPossible());
      Optional.ofNullable(info.getNotAfter()).ifPresent(notAfter -> map.put("notAfter", notAfter));
      map.put("options", options);
      builder.addAdditionalAttribute("migrationInfo", map);
      return builder;
   }

   private boolean isSkipPossible() {
      return ((List)this.migrationOptions.get()).stream().anyMatch(option -> "SKIP_MIGRATION".equals(option.getOption().getName()) && option.isSelectable());
   }

   private boolean isRejectPossible() {
      return ((List)this.migrationOptions.get()).stream().anyMatch(option -> "NEVER_MIGRATE".equals(option.getOption().getName()) && option.isSelectable());
   }

   private List<Option> determineMigrationOptions() {
      return ((List)this.migrationOptions.get())
         .stream()
         .filter(SelectableMigrationOption::isSelectable)
         .map(SelectableMigrationOption::getOption)
         .filter(option -> !"SKIP_MIGRATION".equals(option.getName()))
         .filter(option -> !"NEVER_MIGRATE".equals(option.getName()))
         .toList();
   }
}
