package com.airlock.iam.oauth2.domain.service.codegrant;

import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.flow.shared.api.domain.model.session.UserSession;
import com.airlock.iam.flow.shared.domain.model.session.SuccessfulAuthenticationTimestamp;
import com.airlock.iam.oauth2.application.configuration.OAuth2AuthorizationServerConfigSpec;
import com.airlock.iam.oauth2.domain.model.codegrant.OpenIdConnectAuthenticationRequestAttributes;
import com.airlock.iam.oauth2.domain.model.codegrant.PromptValue;
import com.google.common.annotations.VisibleForTesting;
import jakarta.inject.Inject;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public final class DefaultOpenIdConnectUserSessionValidationService implements OpenIdConnectUserSessionValidationService {
   private final ELogger logger;
   private final UserSession userSession;
   private final Optional<SuccessfulAuthenticationTimestamp> successfulAuthenticationTimestamp;

   @Inject
   public DefaultOpenIdConnectUserSessionValidationService(
      UserSession userSession, Optional<SuccessfulAuthenticationTimestamp> successfulAuthenticationTimestamp, OAuth2AuthorizationServerConfigSpec asConfigSpec
   ) {
      this.userSession = userSession;
      this.successfulAuthenticationTimestamp = successfulAuthenticationTimestamp;
      this.logger = asConfigSpec.getIntegrationLogger(DefaultOpenIdConnectUserSessionValidationService.class);
   }

   @VisibleForTesting
   public DefaultOpenIdConnectUserSessionValidationService(
      ELogger logger, UserSession userSession, Optional<SuccessfulAuthenticationTimestamp> successfulAuthenticationTimestamp
   ) {
      this.logger = logger;
      this.userSession = userSession;
      this.successfulAuthenticationTimestamp = successfulAuthenticationTimestamp;
   }

   @Override
   public boolean isReauthenticationRequired(OpenIdConnectAuthenticationRequestAttributes authenticationRequest) {
      List<PromptValue> prompts = authenticationRequest.prompt();
      return this.isAuthenticatedUserSession()
         && (this.isReauthenticationRequested(prompts) || this.isMaxAgeParameterExceedsTimeSinceUserAuthentication(authenticationRequest));
   }

   private boolean isMaxAgeParameterExceedsTimeSinceUserAuthentication(OpenIdConnectAuthenticationRequestAttributes authenticationRequest) {
      boolean result = authenticationRequest.maxAge()
         .map(
            maxAge -> this.successfulAuthenticationTimestamp
               .map(SuccessfulAuthenticationTimestamp::getAuthenticationTime)
               .map(successfulAuthenticationTime -> successfulAuthenticationTime.isBefore(Instant.now().minus(maxAge)))
               .orElse(false)
         )
         .orElse(false);
      if (result) {
         this.logger
            .debug(
               "Reauthentication of user required because user authentication time exceeds requested max_age value: "
                  + authenticationRequest.maxAge().map(Duration::getSeconds).orElse(0L)
            );
      }

      return result;
   }

   private boolean isReauthenticationRequested(List<PromptValue> prompts) {
      boolean result = prompts.stream().anyMatch(prompt -> prompt.equals(PromptValue.LOGIN) || prompt.equals(PromptValue.SELECT_ACCOUNT));
      if (result) {
         this.logger
            .debug("Reauthentication of user requested by prompt value: " + prompts.stream().map(PromptValue::getParamValue).collect(Collectors.joining(" ")));
      }

      return result;
   }

   private boolean isAuthenticatedUserSession() {
      return this.userSession.userId().isPresent();
   }
}
