package com.airlock.iam.authentication.application.service.targetapp;

import com.airlock.iam.authentication.domain.model.targetapp.TargetApplication;
import com.airlock.iam.base.api.domain.model.exception.ServiceConfigurationException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.flow.ApplicationId;
import com.airlock.iam.flow.application.configuration.FlowConfig;
import com.airlock.iam.flow.application.configuration.FlowConfigRepository;
import com.airlock.iam.flow.application.configuration.flow.FlowConfigHash;
import com.airlock.iam.flow.domain.service.MultiFlowManager;
import com.airlock.iam.login.api.domain.model.location.ForwardLocation;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

public class TargetApplicationRepository implements FlowConfigRepository {
   private static final ELogger LOG = ELoggerFactory.getLogger(TargetApplicationRepository.class);
   private final List<TargetApplication> applications;
   private final Optional<TargetApplication> defaultApplication;
   private final MultiFlowManager flowManager;

   public TargetApplication getDefaultApplication() {
      return ServiceConfigurationException.checkPresent(this.defaultApplication, "No default application configured");
   }

   public TargetApplication getByApplicationId(ApplicationId applicationId) {
      return this.getApplicationById(applicationId, TargetApplication.TargetApplicationType.STANDARD);
   }

   public TargetApplication getOneShotApplicationById(ApplicationId applicationId) {
      return this.getApplicationById(applicationId, TargetApplication.TargetApplicationType.ONE_SHOT);
   }

   private TargetApplication getApplicationById(ApplicationId applicationId, TargetApplication.TargetApplicationType type) {
      return Stream.concat(this.defaultApplication.stream(), this.applications.stream())
         .filter(application -> application.getApplicationType() == type)
         .filter(application -> application.isSelected(applicationId))
         .findFirst()
         .orElseThrow(() -> {
            this.flowManager.destroyCurrentMultiFlow();
            return new TargetApplicationRepository.TargetApplicationNotFoundException(applicationId);
         });
   }

   public TargetApplication getByForwardURIOrDefault(ForwardLocation location) {
      Optional<TargetApplication> selectedApplication = this.getByForwardURI(location);
      if (selectedApplication.isPresent()) {
         LOG.info(
            "Selecting application "
               + LogUtil.quote(selectedApplication.get().getApplicationId())
               + " matching location "
               + LogUtil.logSafeQuote(location.value())
         );
         return selectedApplication.get();
      } else {
         LOG.info("Selecting default application because no application matched given location " + LogUtil.logSafeQuote(location.value()));
         return this.getDefaultApplication();
      }
   }

   public Optional<TargetApplication> getByForwardURI(URI location) {
      return this.getByForwardURI(new ForwardLocation(location));
   }

   private Optional<TargetApplication> getByForwardURI(ForwardLocation location) {
      return this.applications.stream().filter(application -> application.isSelected(location)).findFirst();
   }

   @Override
   public Optional<FlowConfig> getFlowConfigByConfigHash(FlowConfigHash flowConfigHash) {
      return this.defaultApplication
         .<FlowConfig>flatMap(app -> app.getFlowByConfigHash(flowConfigHash))
         .or(() -> this.applications.stream().flatMap(application -> application.getFlowByConfigHash(flowConfigHash).stream()).findFirst());
   }

   @Override
   public Optional<FlowConfig> getDefaultFlowConfig() {
      return this.defaultApplication.map(TargetApplication::getAuthenticationFlow);
   }

   @SuppressFBWarnings(justification = "generated code")
   public TargetApplicationRepository(List<TargetApplication> applications, Optional<TargetApplication> defaultApplication, MultiFlowManager flowManager) {
      this.applications = applications;
      this.defaultApplication = defaultApplication;
      this.flowManager = flowManager;
   }

   public static final class TargetApplicationNotFoundException extends RuntimeException {
      private final ApplicationId applicationId;

      public TargetApplicationNotFoundException(ApplicationId applicationId) {
         super("Target application not found: " + applicationId);
         this.applicationId = applicationId;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ApplicationId getApplicationId() {
         return this.applicationId;
      }
   }
}
