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.service.token.airlock2fa.Airlock2FAAdminApiHardwareTokenService;
import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FAAccountId;
import com.airlock.iam.airlock2fa.api.domain.model.Airlock2FADeviceId;
import com.airlock.iam.common.infrastructure.restclient.RestClient;
import com.airlock.iam.common.infrastructure.restclient.RestClientException;
import com.airlock.iam.common.infrastructure.restclient.RestRequest;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAAdminApiException;
import com.airlock.iam.factor.domain.model.airlock2fa.HardwareTokenArchiveError;
import com.airlock.iam.factor.domain.model.airlock2fa.HardwareTokenSynchronizeError;
import com.airlock.iam.factor.domain.model.airlock2fa.futurae.FuturaeErrorResponseException;
import com.airlock.iam.factor.domain.model.airlock2fa.hardware.Airlock2FAAdminHardwareToken;
import com.airlock.iam.factor.domain.model.airlock2fa.hardware.Airlock2FAAdminHardwareTokenSynchronizationPasscodes;
import com.airlock.iam.factor.domain.model.airlock2fa.hardware.Airlock2FAHardwareTokenFilter;
import com.airlock.iam.factor.domain.model.airlock2fa.hardware.Airlock2FAHardwareTokenId;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeAdminApiHardwareTokenResponseMapper;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeAdminApiRequestFactory;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeResponseReader;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.transfer.FuturaeAdminApiHardwareTokenAssignmentResponse;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.transfer.FuturaeAdminUserHardwareTokensResponse;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response.Status;
import java.util.List;
import java.util.Optional;

public final class FuturaeAdminApiHardwareTokenServiceImpl implements Airlock2FAAdminApiHardwareTokenService {
   private static final ELogger LOG = ELoggerFactory.getLogger(FuturaeAdminApiHardwareTokenServiceImpl.class);
   private final RestClient restClient;
   private final FuturaeAdminApiRequestFactory requestFactory;
   private final Airlock2FAHardwareTokenSettings hardwareTokenSettings;

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

   @Override
   public List<Airlock2FAAdminHardwareToken> searchTokens(Airlock2FAHardwareTokenFilter filter) {
      FuturaeAdminUserHardwareTokensResponse response = this.getHardwareTokens(filter);
      if (response.getTokens() != null) {
         if (!this.hardwareTokenSettings.shareHardwareTokensAmongUsers()) {
            response.getTokens().forEach(MultipleAssignmentHelper::warnOnMultipleAssignments);
         }

         return FuturaeAdminApiHardwareTokenResponseMapper.mapToTokens(response.getTokens());
      } else {
         return List.of();
      }
   }

   @Override
   public Airlock2FADeviceId assignTokenToAccount(Airlock2FAAccountId accountId, Airlock2FAHardwareTokenId tokenId) throws Airlock2FAAdminApiException {
      RestRequest request = this.requestFactory.createHardwareTokenAssignmentRequest(accountId, tokenId);
      LOG.debug(String.format("Assigning hardware token %s to Futurae user %s.", tokenId.getId(), accountId.getId()));

      try {
         return (Airlock2FADeviceId)this.restClient
            .read(
               request,
               response -> {
                  FuturaeAdminApiHardwareTokenAssignmentResponse responseBody = (FuturaeAdminApiHardwareTokenAssignmentResponse)FuturaeResponseReader.readResponse(
                        response, FuturaeAdminApiHardwareTokenAssignmentResponse.class, request
                     )
                     .orElseThrow(Airlock2FAAdminApiException::new);
                  return Airlock2FADeviceId.airlock2FADeviceId(responseBody.getDeviceId());
               }
            );
      } catch (RestClientException | FuturaeErrorResponseException e) {
         throw Airlock2FAAdminApiException.airlock2FAAdminException("Error during hardware token assignment", e);
      }
   }

   @Override
   public Optional<HardwareTokenArchiveError> archiveToken(Airlock2FAHardwareTokenId tokenId) {
      RestRequest request = this.requestFactory.archiveHardwareTokenRequest(tokenId);
      LOG.debug(String.format("Archiving hardware token %s.", tokenId.getId()));

      try {
         return (Optional<HardwareTokenArchiveError>)this.restClient.read(request, response -> {
            if (response.status() == Status.OK.getStatusCode() || response.status() == Status.GONE.getStatusCode()) {
               return Optional.empty();
            } else if (response.status() == Status.NOT_FOUND.getStatusCode() || response.status() == Status.BAD_REQUEST.getStatusCode()) {
               return Optional.of(HardwareTokenArchiveError.NOT_FOUND);
            } else if (response.status() == Status.FORBIDDEN.getStatusCode()) {
               return Optional.of(HardwareTokenArchiveError.ASSIGNED_TO_DIFFERENT_SERVICE);
            } else {
               throw Airlock2FAAdminApiException.airlock2FAAdminException("Unexpected status code when archiving hardware token: " + response.status());
            }
         });
      } catch (RestClientException | FuturaeErrorResponseException e) {
         throw Airlock2FAAdminApiException.airlock2FAAdminException("Error during hardware token assignment", e);
      }
   }

   @Override
   public Optional<HardwareTokenSynchronizeError> synchronizeToken(
      Airlock2FAHardwareTokenId tokenId, Airlock2FAAdminHardwareTokenSynchronizationPasscodes passcodes
   ) {
      RestRequest request = this.requestFactory.synchronizeHardwareTokenRequest(tokenId, passcodes);
      LOG.debug(String.format("Synchronizing hardware token %s.", tokenId.getId()));
      return (Optional<HardwareTokenSynchronizeError>)this.restClient.read(request, response -> {
         if (response.status() == Status.OK.getStatusCode()) {
            return Optional.empty();
         } else if (response.status() == Status.NOT_FOUND.getStatusCode()) {
            return Optional.of(HardwareTokenSynchronizeError.NOT_FOUND);
         } else if (response.status() == Status.BAD_REQUEST.getStatusCode()) {
            return Optional.of(HardwareTokenSynchronizeError.GENERIC_FAILURE);
         } else if (response.status() == Status.FORBIDDEN.getStatusCode()) {
            return Optional.of(HardwareTokenSynchronizeError.ASSIGNED_TO_DIFFERENT_SERVICE);
         } else if (response.status() == Status.GONE.getStatusCode()) {
            return Optional.of(HardwareTokenSynchronizeError.ARCHIVED);
         } else {
            throw Airlock2FAAdminApiException.airlock2FAAdminException("Unexpected status code when synchronizing hardware token: " + response.status());
         }
      });
   }

   private FuturaeAdminUserHardwareTokensResponse getHardwareTokens(Airlock2FAHardwareTokenFilter filter) {
      RestRequest request = this.requestFactory.getHardwareTokensRequest(filter);
      return this.invokeAndMap(request, "Could not retrieve hardware tokens, Request: " + request);
   }

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