package com.airlock.iam.oauth2.infrastructure.service.tokenexchange;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.common.api.domain.model.locking.LockInfo;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.OAuth2ClientAuthentication;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.OAuth2ClientAuthenticationPayload;
import com.airlock.iam.oauth2.api.domain.model.tokenexchange.OAuth2TokenExchangeGrantRequest;
import com.airlock.iam.oauth2.domain.model.client.OAuth2Client;
import com.airlock.iam.oauth2.domain.model.client.OAuth2ClientLoader;
import com.airlock.iam.oauth2.domain.model.logger.IntegrationLoggerFactory;
import com.airlock.iam.oauth2.domain.model.logging.OAuth2LogTypes;
import com.airlock.iam.oauth2.domain.model.tokenexchange.OAuth2TokenExchangeErrorResponse;
import com.airlock.iam.oauth2.domain.model.tokenexchange.OAuth2TokenExchangeGrantAccessToken;
import com.airlock.iam.oauth2.domain.model.validation.OAuth2Error;
import com.airlock.iam.oauth2.domain.service.clientauthentication.OAuth2ClientAuthenticationService;
import com.airlock.iam.oauth2.domain.service.token.OAuth2V3TokenExchangeGrantRequestHandler;
import com.airlock.iam.oauth2.domain.service.tokenexchange.OAuth2TokenExchangeTokenIssuingService;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;

public final class OAuth2V3TokenExchangeGrantRequestHandlerImpl implements OAuth2V3TokenExchangeGrantRequestHandler {
   private final Provider<IntegrationLoggerFactory> loggerFactory;
   private final Provider<OAuth2TokenExchangeGrantRequestParser> tokenExchangeGrantRequestParser;
   private final Provider<OAuth2TokenExchangeTokenIssuingService> tokenIssuingService;
   private final Provider<OAuth2ClientAuthenticationService<OAuth2ClientAuthenticationPayload>> authenticationService;
   private final Provider<OAuth2ClientLoader> clientLoader;

   @Inject
   public OAuth2V3TokenExchangeGrantRequestHandlerImpl(
      Provider<IntegrationLoggerFactory> loggerFactory,
      Provider<OAuth2TokenExchangeGrantRequestParser> tokenExchangeGrantRequestParser,
      Provider<OAuth2TokenExchangeTokenIssuingService> tokenIssuingService,
      Provider<OAuth2ClientAuthenticationService<OAuth2ClientAuthenticationPayload>> authenticationService,
      Provider<OAuth2ClientLoader> clientLoader
   ) {
      this.loggerFactory = loggerFactory;
      this.tokenExchangeGrantRequestParser = tokenExchangeGrantRequestParser;
      this.tokenIssuingService = tokenIssuingService;
      this.authenticationService = authenticationService;
      this.clientLoader = clientLoader;
   }

   @Override
   public Result<OAuth2TokenExchangeGrantAccessToken, OAuth2TokenExchangeErrorResponse> handleRequest(HttpServletRequest request) {
      return ((OAuth2TokenExchangeGrantRequestParser)this.tokenExchangeGrantRequestParser.get())
         .parse(request)
         .mapResult(this::handleSuccessParseResult)
         .orMapFailure(Result::failure);
   }

   private Result<OAuth2TokenExchangeGrantAccessToken, OAuth2TokenExchangeErrorResponse> handleSuccessParseResult(OAuth2TokenExchangeGrantRequest grantRequest) {
      return grantRequest.getAuthenticationPayload()
         .flatMap(authenticationPayload -> ((OAuth2ClientAuthenticationService)this.authenticationService.get()).authorize(authenticationPayload))
         .map(auth -> this.handleAuthenticatedClient(grantRequest, auth))
         .orElseGet(() -> failure(OAuth2Error.INVALID_CLIENT, "Client authentication failed", 401));
   }

   private Result<OAuth2TokenExchangeGrantAccessToken, OAuth2TokenExchangeErrorResponse> handleAuthenticatedClient(
      OAuth2TokenExchangeGrantRequest grantRequest, OAuth2ClientAuthentication authentication
   ) {
      OAuth2ClientId clientId = grantRequest.getClientId();
      Optional<OAuth2Client> client = ((OAuth2ClientLoader)this.clientLoader.get()).tryLoadClient(clientId);
      if (client.isEmpty()) {
         this.getLogger()
            .warn(OAuth2LogTypes.OAUTH2_MESSAGE, String.format("Invalid OAuth 2.0 request: Client with ID %s not found", LogUtil.logSafeQuote(clientId)));
         return failure(OAuth2Error.INVALID_CLIENT, "Invalid client", 401);
      } else {
         Optional<LockInfo> lockInfo = client.get().lockInfo();
         if (lockInfo.isPresent()) {
            this.getLogger()
               .warn(
                  OAuth2LogTypes.OAUTH2_MESSAGE,
                  String.format(
                     "Invalid OAuth 2.0 request: Client with ID %s is locked with reason %s.",
                     LogUtil.logSafeQuote(clientId),
                     LogUtil.logSafeQuote(lockInfo.get().getLockReason())
                  )
               );
            return failure(OAuth2Error.UNAUTHORIZED_CLIENT, "Client locked", 400);
         } else {
            return ((OAuth2TokenExchangeTokenIssuingService)this.tokenIssuingService.get()).issueAccessToken(client.get(), authentication, grantRequest);
         }
      }
   }

   private static Result<OAuth2TokenExchangeGrantAccessToken, OAuth2TokenExchangeErrorResponse> failure(OAuth2Error error, String description, int statusCode) {
      return Result.failure(OAuth2TokenExchangeErrorResponse.tokenExchangeErrorResponse(error, description, statusCode));
   }

   private ELogger getLogger() {
      return ((IntegrationLoggerFactory)this.loggerFactory.get()).create(OAuth2V3TokenExchangeGrantRequestHandlerImpl.class);
   }
}
