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

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.infrastructure.uri.ExternalUriInfo;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.ClientAuthenticationMethod;
import com.airlock.iam.oauth2.api.domain.service.clientauthentication.OAuth2ClientAuthentication;
import com.airlock.iam.oauth2.api.domain.service.clientauthentication.OAuth2ClientAuthenticationParser;
import com.airlock.iam.oauth2.api.domain.service.clientauthentication.OAuth2ClientAuthenticationVerifier;
import com.airlock.iam.oauth2.application.configuration.clientauthentication.OpenIdConnectPrivateKeyJwtAuthenticationConfig;
import com.airlock.iam.oauth2.domain.model.client.OAuth2ClientLoader;
import com.airlock.iam.oauth2.domain.model.clientauthentication.OAuth2AcceptedClientAssertionRepository;
import com.airlock.iam.oauth2.domain.model.clientauthentication.OpenIdConnectPrivateKeyJwtAuthenticationPayload;
import com.airlock.iam.oauth2.domain.model.clientauthentication.method.KnownClientAuthenticationMethod;
import com.airlock.iam.oauth2.domain.model.metadata.OAuth2AsCentricEndpointUriComposer;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.net.URI;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@LogicForConfig(OpenIdConnectPrivateKeyJwtAuthenticationConfig.class)
public final class OpenIdConnectPrivateKeyJwtAuthentication implements OAuth2ClientAuthentication<OpenIdConnectPrivateKeyJwtAuthenticationPayload> {
   private static final Pattern VALID_URI_PATTERN = Pattern.compile("(.*)/(par|token)/?");
   private static final List<ClientAuthenticationMethod> CLIENT_AUTHENTICATION_METHODS = List.of(KnownClientAuthenticationMethod.PRIVATE_KEY_JWT);
   private static final OpenIdConnectPrivateKeyJwtAuthenticationParser PARSER = new OpenIdConnectPrivateKeyJwtAuthenticationParser();
   private final Provider<OAuth2ClientLoader> clientLoader;
   private final Provider<TimeProvider> timeProvider;
   private final Provider<ExternalUriInfo> externalUriInfo;
   private final Provider<OAuth2AcceptedClientAssertionRepository> jtiRepository;

   @Inject
   public OpenIdConnectPrivateKeyJwtAuthentication(
      Provider<OAuth2ClientLoader> clientLoader,
      Provider<TimeProvider> timeProvider,
      Provider<ExternalUriInfo> externalUriInfo,
      Provider<OAuth2AcceptedClientAssertionRepository> jtiRepository
   ) {
      this.clientLoader = clientLoader;
      this.timeProvider = timeProvider;
      this.externalUriInfo = externalUriInfo;
      this.jtiRepository = jtiRepository;
   }

   @Override
   public List<ClientAuthenticationMethod> supportedAuthenticationMethods() {
      return CLIENT_AUTHENTICATION_METHODS;
   }

   @Override
   public OAuth2ClientAuthenticationParser<OpenIdConnectPrivateKeyJwtAuthenticationPayload> authenticationParser() {
      return PARSER;
   }

   @Override
   public OAuth2ClientAuthenticationVerifier<OpenIdConnectPrivateKeyJwtAuthenticationPayload> verifier() {
      return OpenIdConnectPrivateKeyJwtAuthenticationVerifier.builder()
         .clientLoader((OAuth2ClientLoader)this.clientLoader.get())
         .timeProvider((TimeProvider)this.timeProvider.get())
         .uriComposer(
            OAuth2AsCentricEndpointUriComposer.asCentricEndpointUriComposer(stripTokenOrPar(((ExternalUriInfo)this.externalUriInfo.get()).getAbsolutePath()))
         )
         .jtiRepository((OAuth2AcceptedClientAssertionRepository)this.jtiRepository.get())
         .build();
   }

   private static URI stripTokenOrPar(URI path) {
      Matcher matcher = VALID_URI_PATTERN.matcher(path.toASCIIString());
      ViolationException.checkTrue(matcher.matches(), "Only par and token endpoints allowed");
      return URI.create(matcher.group(1));
   }
}
