package io.netfoundry.sds.config;

import java.util.UUID;
import java.util.function.Supplier;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwk.HttpsJwks;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver;
import org.jose4j.keys.resolvers.VerificationKeyResolver;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import io.netfoundry.auth.client.midleware.Auth0JwtValidator;
import io.netfoundry.auth.client.midleware.InterServiceTokenProvider;
import io.netfoundry.auth.clientv2.AuthClientV2;
import io.netfoundry.auth.clientv2.AuthorizationContext;
import io.netfoundry.auth.clientv2.InvalidTokenControllerAdvice;
import io.netfoundry.auth.clientv2.StandardResourceResolver;
import io.netfoundry.auth.clientv2.WhereClauseHandlerMethodArgumentResolver;
import io.netfoundry.auth.clientv2.WhereClauseHandlerMethodArgumentResolver.ResourceWhereClauseResolver;
import io.netfoundry.auth.domainv2.AuthEntityResolver;
import io.netfoundry.auth.domainv2.AuthorizationStrategy;
import io.netfoundry.auth.domainv2.domain.Domain;
import io.netfoundry.common.util.RestServiceClient;
import io.netfoundry.sds.external.TokenProvider;
import jakarta.validation.Validator;

@Import({
    InvalidTokenControllerAdvice.class,
    AuthorizationStrategy.AutoConfig.class,
    AuthorizationContext.class
})
@Configuration
public class AuthorizationConfig {

  @Bean
  public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
    MethodValidationPostProcessor mvpp = new MethodValidationPostProcessor();
    mvpp.setValidator(validator); // must set this or it uses a non-spring aware version.
    return mvpp;
  }

  @Bean
  @Profile("!test")
  VerificationKeyResolver jwtVerificationResolver(@Value("${io.netfoundry.auth.serviceUrl}") String authServiceUrl) {
    HttpsJwks httpsJkws = new HttpsJwks(authServiceUrl + "/auth/jwks");
    return new HttpsJwksVerificationKeyResolver(httpsJkws);
  }

  @Bean
  public JwtConsumer jwtConsumer(VerificationKeyResolver jwtVerificationResolver) {
    return new JwtConsumerBuilder()
        .setAllowedClockSkewInSeconds(60)
        .setJwsAlgorithmConstraints(AlgorithmConstraints.DISALLOW_NONE)
        .setEnableRequireIntegrity()
        .setVerificationKeyResolver(jwtVerificationResolver)
        .setExpectedAudience("io.netfoundry.management", "io.netfoundry.apps")  // TODO remove apps when survey migrated to new service
        .setExpectedIssuer("NetFoundry")
        .build();
  }

  @Bean
  @Profile("!test")
  public Domain domain(AuthEntityResolver authEntityResolver) {
    return authEntityResolver.getDomain("io.netfoundry.management")
        .orElseThrow(() -> new IllegalStateException("Failed to load default Domain, io.netfoundry.management, from the Auth Service."));
  }

  @Bean
  @Profile("!test")
  public StandardResourceResolver resourceResolver(
      AuthClientV2 authClient,
      Domain defaultDomain,
      @Value("${io.netfoundry.auth.pathCacheTtlSeconds:21600}") int pathCacheTtlSeconds,
      @Value("${io.netfoundry.auth.resourceActionCacheTtlSeconds:21600}") int resourceActionCacheTtlSeconds) {
    return new StandardResourceResolver(authClient, defaultDomain, pathCacheTtlSeconds, resourceActionCacheTtlSeconds);
  }

  @Bean
  @Profile("!test")
  AuthClientV2 authClientV2(
      RestServiceClient restServiceClient,
      InterServiceTokenProvider tokenProvider,
      @Value("${io.netfoundry.auth.serviceUrl}") String authServiceUrl) {
    return new AuthClientV2(restServiceClient, tokenProvider, authServiceUrl);
  }

  @Bean
  @Profile("!test")
  public Auth0JwtValidator auth0JwtValidator(
      @Value("${io.netfoundry.auth.provider.auth0.issuer}") String issuer,
      @Value("${io.netfoundry.auth.provider.auth0.audience}") String audience
  ) {
    return new Auth0JwtValidator(issuer, audience, /*isLogAsError*/ true);
  }


  @Bean
  @Profile("!test")
  public InterServiceTokenProvider interServiceTokenProvider(
      TaskScheduler taskScheduler,
      @Value("${io.netfoundry.customer.provider.auth0.clientId}") String clientId,
      @Value("${io.netfoundry.customer.provider.auth0.clientSecret}") String clientSecret,
      Auth0JwtValidator auth0JwtValidator,
      RestServiceClient restServiceClient,
      @Value("${io.netfoundry.auth.serviceUrl}") String authServiceUrl
  ) {
    return new InterServiceTokenProvider(
        taskScheduler,
        () -> clientId,
        () -> clientSecret,
        restServiceClient,
        auth0JwtValidator,
        authServiceUrl
    );
  }

  @Bean
  public TokenProvider tokenProvider(final InterServiceTokenProvider interServiceTokenProvider) {
    return TokenProvider.tokenProviderAdapter(interServiceTokenProvider);
  }

  @Bean
  @Profile("!test")
  public Supplier<UUID> serviceIdentityIdProvider(InterServiceTokenProvider tokenProvider) {
    return tokenProvider::getLocalServiceIdentity;
  }

  @Bean
  public WhereClauseHandlerMethodArgumentResolver whereClauseHandlerMethodArgumentResolver(
      StandardResourceResolver resourceResolver,
      ResourceWhereClauseResolver resourceWhereClauseResolver) {
    return new WhereClauseHandlerMethodArgumentResolver(resourceResolver, resourceWhereClauseResolver);
  }

}
