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

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.core.misc.authen.SecretStringGenerator;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.api.domain.model.AuthorizationServerId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientSecret;
import com.airlock.iam.techclient.api.domain.model.oauth2.MtlsCertificate;
import com.airlock.iam.techclient.api.domain.model.oauth2.OAuth2TechnicalClientMetadata;
import com.airlock.iam.techclient.api.domain.model.oauth2.OAuth2TechnicalClientMetadataBuilder;
import com.airlock.iam.techclient.auth.domain.model.oauth2.LoginOAuth2TechClientImpl;
import com.airlock.iam.techclient.auth.domain.model.oauth2.OAuth2TechClientMetadataImpl;
import com.airlock.iam.techclient.domain.model.identity.TechClientIdentityFactory;
import com.airlock.iam.techclientreg.api.domain.model.oauth2.OAuth2ClientRegistrationError;
import com.airlock.iam.techclientreg.api.domain.model.oauth2.OAuth2ClientRegistrationRequest;
import com.airlock.iam.techclientreg.api.domain.model.request.CertificateRequestCredential;
import com.airlock.iam.techclientreg.api.domain.model.request.RequestCredential;
import com.airlock.iam.techclientreg.api.domain.service.oauth2.OAuth2ClientMetadataProcessor;
import com.airlock.iam.techclientreg.application.configuration.oauth2dcr.DefaultOAuth2ClientRegistrationConfig;
import com.airlock.iam.techclientreg.application.service.oauth2.clientid.DcrClientIdGenerator;
import com.airlock.iam.techclientreg.domain.model.registration.GrantProcessor;
import com.airlock.iam.techclientreg.domain.service.OAuth2ClientRegistrationLogic;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@LogicForConfig(DefaultOAuth2ClientRegistrationConfig.class)
public class DefaultOAuth2TechClientRegistrationLogic implements OAuth2ClientRegistrationLogic {
   private static final ELogger LOG = ELoggerFactory.getLogger(DefaultOAuth2TechClientRegistrationLogic.class);
   public static final String FALLBACK_DISPLAY_NAME = "DCR OAuth 2.0 Client";
   private final Provider<Optional<RequestCredential>> credential;
   private final Provider<DcrClientIdGenerator> clientIdGenerator;
   private final Provider<Optional<SecretStringGenerator>> clientSecretGenerator;
   private final Provider<List<GrantProcessor>> grantProcessors;
   private final Provider<List<OAuth2ClientMetadataProcessor>> additionalProcessors;
   private final Provider<AuthorizationServerId> authorizationServerId;

   @Inject
   public DefaultOAuth2TechClientRegistrationLogic(
      Provider<Optional<RequestCredential>> credential,
      Provider<DcrClientIdGenerator> clientIdGenerator,
      Provider<Optional<SecretStringGenerator>> clientSecretGenerator,
      Provider<List<GrantProcessor>> grantProcessors,
      Provider<List<OAuth2ClientMetadataProcessor>> additionalProcessors,
      Provider<AuthorizationServerId> authorizationServerId
   ) {
      this.credential = credential;
      this.clientIdGenerator = clientIdGenerator;
      this.clientSecretGenerator = clientSecretGenerator;
      this.grantProcessors = grantProcessors;
      this.additionalProcessors = additionalProcessors;
      this.authorizationServerId = authorizationServerId;
   }

   @Override
   public OAuth2ClientRegistrationLogic.OAuth2ClientRegistrationResponse registerClient(OAuth2ClientRegistrationRequest registrationRequest) {
      Optional<OAuth2ClientId> clientId = ((DcrClientIdGenerator)this.clientIdGenerator.get()).generateClientId(registrationRequest);
      if (!clientId.isEmpty() && this.validateClientId(clientId.get())) {
         OAuth2TechnicalClientMetadataBuilder builder = OAuth2TechClientMetadataImpl.builder(
               clientId.get(), (AuthorizationServerId)this.authorizationServerId.get()
            )
            .clientIdIssuedAt(Optional.of(Instant.now()))
            .mtlsCertificate(this.createMtlsCertificate());

         for (OAuth2ClientMetadataProcessor handler : this.allProcessors()) {
            Optional<OAuth2ClientRegistrationError> error = handler.updateClient(registrationRequest, builder);
            if (error.isPresent()) {
               return OAuth2ClientRegistrationLogic.OAuth2ClientRegistrationResponse.failure(error.get());
            }
         }

         builder.clientSecret(this.generateClientSecretIfNeeded(builder.build().getTokenEndpointAuthMethod()));
         OAuth2TechnicalClientMetadata metadata = builder.build();
         return OAuth2ClientRegistrationLogic.OAuth2ClientRegistrationResponse.success(
            LoginOAuth2TechClientImpl.builder()
               .id(TechClientIdentityFactory.techClientId(UUID.randomUUID().toString()))
               .displayName(metadata.getClientName().orElse("DCR OAuth 2.0 Client"))
               .metadata(metadata)
               .lockInfo(Optional.empty())
               .build()
         );
      } else {
         return OAuth2ClientRegistrationLogic.OAuth2ClientRegistrationResponse.failure(OAuth2ClientRegistrationError.invalidClientMetadata("Invalid clientId"));
      }
   }

   private boolean validateClientId(OAuth2ClientId clientId) {
      boolean clientIdIsValid = OAuth2ClientId.ALLOWED_CLIENTID_CHARACTERS_PATTERN.matcher(clientId.id()).matches();
      if (!clientIdIsValid) {
         LOG.info("Client ID contains invalid characters: " + LogUtil.logSafeQuote(clientId.id()));
      }

      return clientIdIsValid;
   }

   private Optional<OAuth2ClientSecret> generateClientSecretIfNeeded(Optional<String> tokenEndpointAuthMethod) {
      return ((Optional)this.clientSecretGenerator.get()).isPresent() && this.requiresClientSecret(tokenEndpointAuthMethod)
         ? Optional.of(new OAuth2ClientSecret(((SecretStringGenerator)((Optional)this.clientSecretGenerator.get()).get()).createString(null)))
         : Optional.empty();
   }

   private boolean requiresClientSecret(Optional<String> tokenEndpointAuthMethod) {
      return tokenEndpointAuthMethod.isPresent() && tokenEndpointAuthMethod.get().startsWith("client_secret_");
   }

   private Optional<MtlsCertificate> createMtlsCertificate() {
      return ((Optional)this.credential.get())
         .filter(CertificateRequestCredential.class::isInstance)
         .map(CertificateRequestCredential.class::cast)
         .map(credential -> MtlsCertificate.builder().issuer(credential.getIssuer()).subject(credential.getSubject()).build());
   }

   private List<OAuth2ClientMetadataProcessor> allProcessors() {
      List<OAuth2ClientMetadataProcessor> list = new ArrayList<>();
      list.addAll((Collection<? extends OAuth2ClientMetadataProcessor>)this.grantProcessors.get());
      list.addAll((Collection<? extends OAuth2ClientMetadataProcessor>)this.additionalProcessors.get());
      return list;
   }
}
