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

import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FAAccountId;
import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FADeviceId;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.factor.application.configuration.airlock2fa.Airlock2FASettings;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAuthTokenId;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FADeviceInformation;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAUser;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAUserInformation;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FACooldownInformationService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FACooldownInformationServiceImpl;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAUserInformationService;
import com.airlock.iam.factor.domain.service.airlock2fa.repository.Airlock2FAUserRepository;
import com.airlock.iam.flow.shared.api.domain.model.valueprovider.ValueMapProvider;
import com.airlock.iam.flow.shared.application.configuration.airlock2fa.Airlock2FAAuthenticationDataValueMapProviderConfig;
import com.airlock.iam.flow.shared.domain.service.airlock2fa.Airlock2FAFlowSessionAttributesService;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@LogicForConfig(Airlock2FAAuthenticationDataValueMapProviderConfig.class)
public final class Airlock2FAAuthenticationDataValueMapProvider implements ValueMapProvider {
   private final Provider<PrincipalInfo> user;
   private final Provider<Airlock2FAUserRepository> repository;
   private final Provider<Airlock2FAUserInformationService> userService;
   private final Provider<Airlock2FAFlowSessionAttributesService> attributesService;
   private final Provider<Airlock2FACooldownInformationService> cooldownService;
   private final Provider<Airlock2FASettings> airlock2FASettings;

   @Inject
   public Airlock2FAAuthenticationDataValueMapProvider(
      Provider<PrincipalInfo> user,
      Provider<Airlock2FAUserRepository> repository,
      Provider<Airlock2FAUserInformationService> userService,
      Provider<Airlock2FAFlowSessionAttributesService> attributesService,
      Provider<Airlock2FACooldownInformationService> cooldownService,
      Provider<Airlock2FASettings> airlock2FASettings
   ) {
      this.user = user;
      this.repository = repository;
      this.userService = userService;
      this.attributesService = attributesService;
      this.cooldownService = cooldownService;
      this.airlock2FASettings = airlock2FASettings;
   }

   @Override
   public Map<String, Object> provideValueMap() {
      List<Airlock2FADeviceInformation> registeredDevices = this.accountId().map(this::loadRegisteredDevices).orElse(Collections.emptyList());
      Optional<Airlock2FADeviceInformation> authTokenDevice = this.getAuthTokenDevice(registeredDevices);
      Map<String, Object> result = new HashMap<>();
      if (authTokenDevice.isPresent()) {
         result.put("a2fa-auth-token-device-id", authTokenDevice.get().getId().id());
         result.put("a2fa-auth-token-device-type", authTokenDevice.get().getDeviceType().getDisplayName());
         result.put("a2fa-auth-token-device-display-name", authTokenDevice.get().getDisplayName().getValue());
         result.put("a2fa-auth-token-device-enrollment-timestamp", Date.from(authTokenDevice.get().getEnrolledAt()));
      }

      Optional<Airlock2FAAuthDeviceAttribute> cooldownAuthDevice = this.getCooldownAuthDevice();
      cooldownAuthDevice.ifPresent(value -> result.put("a2fa-cooldown-auth-device", value.cooldownStatus().getDisplayName()));
      this.getCooldownEnds(cooldownAuthDevice).ifPresent(cooldownPeriodEnds -> result.put("a2fa-cooldown-ends", Date.from(cooldownPeriodEnds)));
      Optional<Duration> cooldownPeriod = ((Airlock2FASettings)this.airlock2FASettings.get()).getCooldownDuration();
      Airlock2FACooldownInformationServiceImpl.Airlock2FAUserCooldownState cooldownState = ((Airlock2FACooldownInformationService)this.cooldownService.get())
         .determineUserCooldownState(registeredDevices, cooldownPeriod);
      result.put("a2fa-cooldown-devices", cooldownState.getDisplayName());
      return result;
   }

   private Optional<Airlock2FADeviceInformation> getAuthTokenDevice(List<Airlock2FADeviceInformation> registeredDevices) {
      return this.getDeviceWithId(registeredDevices, this.getAirlock2FAAuthTokenDeviceId());
   }

   private Optional<Airlock2FAAccountId> accountId() {
      return ((PrincipalInfo)this.user.get())
         .getKnownIdentity()
         .map(UserIdentity::id)
         .flatMap(id -> ((Airlock2FAUserRepository)this.repository.get()).findBy(id))
         .map(Airlock2FAUser::getAccountId);
   }

   private List<Airlock2FADeviceInformation> loadRegisteredDevices(Airlock2FAAccountId accountId) {
      return ((Airlock2FAUserInformationService)this.userService.get())
         .getUser(accountId)
         .map(Airlock2FAUserInformation::getDevices)
         .orElseThrow(() -> new FailureException("Expected Futurae to provide user information"));
   }

   private Optional<Airlock2FADeviceId> getAirlock2FAAuthTokenDeviceId() {
      return ((Airlock2FAFlowSessionAttributesService)this.attributesService.get()).loadAuthTokenId().map(Airlock2FAAuthTokenId::deviceId);
   }

   private Optional<Airlock2FADeviceInformation> getDeviceWithId(List<Airlock2FADeviceInformation> registeredDevices, Optional<Airlock2FADeviceId> deviceId) {
      return deviceId.flatMap(id -> registeredDevices.stream().filter(device -> device.getId().equals(id)).findFirst());
   }

   private Optional<Airlock2FAAuthDeviceAttribute> getCooldownAuthDevice() {
      return ((Airlock2FAFlowSessionAttributesService)this.attributesService.get()).loadAuthDevice();
   }

   private Optional<Instant> getCooldownEnds(Optional<Airlock2FAAuthDeviceAttribute> authDevice) {
      return authDevice.flatMap(Airlock2FAAuthDeviceAttribute::cooldownEnds);
   }
}
