package com.airlock.iam.admin.infrastructure.service.token.airlock2fa;

import com.airlock.iam.admin.application.service.token.airlock2fa.Airlock2FAHardwareTokenSettings;
import com.airlock.iam.admin.domain.model.token.airlock2fa.Airlock2FAAdminAppDevice;
import com.airlock.iam.admin.domain.service.token.airlock2fa.Airlock2FAAdminApiDeviceService;
import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FAAccountId;
import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FADeviceId;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.infrastructure.restclient.RestClient;
import com.airlock.iam.common.infrastructure.restclient.RestRequest;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAdminApiException;
import com.airlock.iam.factor.domain.model.airlock2fa.hardware.Airlock2FAAdminHardwareDevice;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeAdminApiRequestFactory;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeHardwareDevice;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeResponseReader;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.transfer.FuturaeAdminUserDeviceResponse;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.transfer.FuturaeHardwareToken;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.transfer.FuturaeUserDevice;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public final class FuturaeAdminApiDeviceServiceImpl implements Airlock2FAAdminApiDeviceService {
   private final RestClient restClient;
   private final FuturaeAdminApiRequestFactory requestFactory;
   private final Airlock2FAHardwareTokenSettings settings;

   @Inject
   public FuturaeAdminApiDeviceServiceImpl(RestClient restClient, FuturaeAdminApiRequestFactory requestFactory, Airlock2FAHardwareTokenSettings settings) {
      this.restClient = restClient;
      this.requestFactory = requestFactory;
      this.settings = settings;
   }

   @Override
   public Airlock2FAAdminApiDeviceService.AdminDevices getEnrolledDevices(Airlock2FAAccountId accountId) throws Airlock2FAAdminApiException {
      FuturaeAdminUserDeviceResponse response = this.getEnrolledUserDevices(accountId);
      return new FuturaeAdminApiDeviceServiceImpl.AdminDevicesImpl(
         FuturaeAdminApiDeviceResponseMapper.toAppDevices(this.filterAppDevices(response.getDevices())),
         FuturaeAdminApiDeviceResponseMapper.toHardwareDevices(this.enhanceHardwareDevicesWithTokenModel(response.getDevices()))
      );
   }

   @Override
   public String getDeviceAssignee(Airlock2FADeviceId deviceId) throws Airlock2FAAdminApiException {
      FuturaeUserDevice response = this.invokeAndMap(
         this.requestFactory.getDevice(deviceId.getId()),
         String.format("Could not get device with ID %s", LogUtil.quote(deviceId.getId())),
         FuturaeUserDevice.class
      );
      return response.getUserId();
   }

   private Collection<FuturaeUserDevice> filterAppDevices(List<FuturaeUserDevice> devices) {
      return devices.stream().filter(d -> "android".equals(d.getType()) || "ios".equals(d.getType())).collect(Collectors.toList());
   }

   private Collection<FuturaeHardwareDevice> enhanceHardwareDevicesWithTokenModel(List<FuturaeUserDevice> devices) {
      return devices.stream().filter(d -> "hwtoken".equals(d.getType())).map(this::getHardwareDevice).collect(Collectors.toList());
   }

   private FuturaeHardwareDevice getHardwareDevice(FuturaeUserDevice userDevice) {
      return new FuturaeHardwareDevice(userDevice, this.getHardwareToken(userDevice.getHardwareTokenId()));
   }

   private FuturaeHardwareToken getHardwareToken(String hardwareTokenId) {
      FuturaeHardwareToken token = this.invokeAndMap(
         this.requestFactory.getHardwareToken(hardwareTokenId),
         String.format("Could not get hardware token for user device with id %s", LogUtil.quote(hardwareTokenId)),
         FuturaeHardwareToken.class
      );
      if (!this.settings.shareHardwareTokensAmongUsers()) {
         MultipleAssignmentHelper.warnOnMultipleAssignments(token);
      }

      return token;
   }

   @Override
   public void unenrollDevice(Airlock2FADeviceId deviceId) {
      this.invoke(this.requestFactory.unenrollDevice(deviceId.getId()), String.format("Could not unenroll device with id %s", LogUtil.quote(deviceId.getId())));
   }

   private FuturaeAdminUserDeviceResponse getEnrolledUserDevices(Airlock2FAAccountId accountId) {
      return this.invokeAndMap(
         this.requestFactory.getEnrolledDevices(accountId.getId()),
         String.format("Could not get devices for user with id %s", LogUtil.quote(accountId.getId())),
         FuturaeAdminUserDeviceResponse.class
      );
   }

   private void invoke(RestRequest request, String errorInfo) {
      this.restClient
         .send(request, response -> FuturaeResponseReader.ifErrorResponseThrow(response, request, e -> new Airlock2FAAdminApiException(errorInfo + ". " + e)));
   }

   private <T> T invokeAndMap(RestRequest request, String errorInfo, Class<T> responseClass) {
      return (T)this.restClient
         .read(
            request,
            response -> FuturaeResponseReader.readResponse(response, responseClass, request).orElseThrow(e -> new Airlock2FAAdminApiException(errorInfo, e))
         );
   }

   public static final class AdminDevicesImpl implements Airlock2FAAdminApiDeviceService.AdminDevices {
      private final List<Airlock2FAAdminAppDevice> appDevices;
      private final List<Airlock2FAAdminHardwareDevice> hardwareDevices;

      public AdminDevicesImpl() {
         this.appDevices = List.of();
         this.hardwareDevices = List.of();
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public List<Airlock2FAAdminAppDevice> appDevices() {
         return this.appDevices;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public List<Airlock2FAAdminHardwareDevice> hardwareDevices() {
         return this.hardwareDevices;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof FuturaeAdminApiDeviceServiceImpl.AdminDevicesImpl other)) {
            return false;
         } else {
            Object this$appDevices = this.appDevices;
            Object other$appDevices = other.appDevices;
            if (this$appDevices == null ? other$appDevices == null : this$appDevices.equals(other$appDevices)) {
               Object this$hardwareDevices = this.hardwareDevices;
               Object other$hardwareDevices = other.hardwareDevices;
               return this$hardwareDevices == null ? other$hardwareDevices == null : this$hardwareDevices.equals(other$hardwareDevices);
            } else {
               return false;
            }
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         Object $appDevices = this.appDevices;
         result = result * 59 + ($appDevices == null ? 43 : $appDevices.hashCode());
         Object $hardwareDevices = this.hardwareDevices;
         return result * 59 + ($hardwareDevices == null ? 43 : $hardwareDevices.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "FuturaeAdminApiDeviceServiceImpl.AdminDevicesImpl(appDevices=" + this.appDevices() + ", hardwareDevices=" + this.hardwareDevices() + ")";
      }

      @SuppressFBWarnings(justification = "generated code")
      public AdminDevicesImpl(List<Airlock2FAAdminAppDevice> appDevices, List<Airlock2FAAdminHardwareDevice> hardwareDevices) {
         this.appDevices = appDevices;
         this.hardwareDevices = hardwareDevices;
      }
   }
}
