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

import com.airlock.iam.authentication.domain.model.idpropagation.TargetUriToPropagate;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.flow.FlowResult;
import com.airlock.iam.flow.api.domain.model.session.FlowAttributeRepository;
import com.airlock.iam.flow.application.service.flow.FlowTerminationService;
import com.airlock.iam.oauth2.domain.model.codegrant.OAuth2AuthorizationCodeGrantFlowSessionAttribute;
import com.airlock.iam.oauth2.domain.model.codegrant.OAuth2OidcAuthorizationRequestAttributes;
import com.airlock.iam.oauth2.domain.model.response.OAuth2ErrorResponseBuilder;
import com.airlock.iam.oauth2.domain.model.validation.OAuth2Error;
import com.airlock.iam.oauth2.domain.service.OpenIdProviderUserAgentStateService;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.net.URI;
import java.util.Optional;

public final class OpenIdConnectPromptNoneAuthorizationServiceImpl implements OpenIdConnectPromptNoneAuthorizationService {
   private static final ELogger LOG = ELoggerFactory.getLogger(OpenIdConnectPromptNoneAuthorizationServiceImpl.class);
   private final Provider<OAuth2AuthorizationService> authorizationService;
   private final Provider<TargetUriToPropagate> targetUri;
   private final Provider<FlowAttributeRepository> attributes;
   private final Provider<FlowTerminationService> flowTerminationService;
   private final Provider<OpenIdProviderUserAgentStateService> sessionStateService;

   @Inject
   public OpenIdConnectPromptNoneAuthorizationServiceImpl(
      Provider<OAuth2AuthorizationService> authorizationService,
      Provider<TargetUriToPropagate> targetUri,
      Provider<FlowAttributeRepository> attributes,
      Provider<FlowTerminationService> flowTerminationService,
      Provider<OpenIdProviderUserAgentStateService> sessionStateService
   ) {
      this.authorizationService = authorizationService;
      this.targetUri = targetUri;
      this.attributes = attributes;
      this.flowTerminationService = flowTerminationService;
      this.sessionStateService = sessionStateService;
   }

   @Override
   public Optional<URI> accessWithPromptNone(URI requestUri) {
      try {
         return this.accessInternal(requestUri).or(() -> this.createErrorRedirect(OAuth2Error.SERVER_ERROR));
      } catch (RuntimeException e) {
         LOG.info("Exception while trying OIDC authorization with prompt=none, returning 'server error': " + e.getMessage());
         return this.createErrorRedirect(OAuth2Error.SERVER_ERROR);
      }
   }

   private Optional<URI> accessInternal(URI requestUri) {
      FlowResult result = ((OAuth2AuthorizationService)this.authorizationService.get()).authenticationRequest(requestUri.getQuery(), true);
      if (result.flowCompletedSuccessfully()) {
         return ((TargetUriToPropagate)this.targetUri.get()).getTargetUri().or(() -> {
            throw new FailureException("No URI has been propagated, possibly there's no 'OAuth 2.0/OIDC ID Propagator' configured");
         });
      }

      if (isInvalidAuthorizationError(result)) {
         return Optional.ofNullable((URI)result.additionalAttributes().get("clientRedirectUri")).or(() -> {
            throw new FailureException("Flow result is an OAuth2 error but does not contain a client redirect uri");
         });
      }

      LOG.info("Internal error in flow, returning 'server error'.");
      ((FlowTerminationService)this.flowTerminationService.get()).abortFlow();
      return this.createErrorRedirect(OAuth2Error.SERVER_ERROR);
   }

   private Optional<URI> createErrorRedirect(OAuth2Error error) {
      return this.createErrorRedirect(error, Optional.empty());
   }

   private Optional<URI> createErrorRedirect(OAuth2Error error, Optional<String> errorDescription) {
      try {
         return this.getRequest()
            .map(this::errorRedirectBuilder)
            .map(
               redirectBuilder -> errorDescription.<URI>map(description -> redirectBuilder.buildResponse(error, description).redirectUri())
                  .orElseGet(() -> redirectBuilder.buildResponse(error).redirectUri())
            );
      } catch (RuntimeException e) {
         LOG.info("Exception while trying to determine error redirect: " + e.getMessage());
         return Optional.empty();
      }
   }

   private Optional<OAuth2OidcAuthorizationRequestAttributes> getRequest() {
      return ((FlowAttributeRepository)this.attributes.get())
         .tryGetAttribute(OAuth2AuthorizationCodeGrantFlowSessionAttribute.class)
         .map(OAuth2AuthorizationCodeGrantFlowSessionAttribute::request);
   }

   private OAuth2ErrorResponseBuilder errorRedirectBuilder(OAuth2OidcAuthorizationRequestAttributes request) {
      return OAuth2ErrorResponseBuilder.errorResponseBuilder(
         ((OpenIdProviderUserAgentStateService)this.sessionStateService.get()).state(),
         request.clientId(),
         request.effectiveRedirectUri(),
         request.state(),
         request.responseMode()
      );
   }

   private static boolean isInvalidAuthorizationError(FlowResult result) {
      return result.error().isPresent()
         && result.error()
            .get()
            .name()
            .equals(OAuth2AuthorizationService.OAuth2AuthorizationEndpointErrors.OAUTH2_AUTHORIZATION_SERVER_INVALID_AUTHORIZATION_REQUEST.name());
   }
}
