package cz.muni.fi.pa165.soccermanager.configuration;

import static cz.muni.fi.pa165.soccermanager.ApplicationConstants.API_URL;

import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;

@Log4j2
@EnableGlobalMethodSecurity(
    prePostEnabled = true
)
@Configuration
@RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  public static final String AUTHORITIES_CLAIM_NAME = "roles";

  @Value("${app.security.jwt.keystore-location}")
  private String keyStorePath;

  @Value("${app.security.jwt.keystore-password}")
  private String keyStorePassword;

  @Value("${app.security.jwt.key-alias}")
  private String keyAlias;

  @Value("${app.security.jwt.private-key-passphrase}")
  private String privateKeyPassphrase;

  private final SecurityProblemSupport securityProblemSupport;

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  @Primary
  @Override
  protected UserDetailsService userDetailsService() {
    final var manager = new InMemoryUserDetailsManager();

    final var user1 = User
        .withUsername("tita")
        .authorities("ADMIN")
        .passwordEncoder(passwordEncoder()::encode)
        .password("1234")
        .build();
    manager.createUser(user1);

    final var user2 = User
        .withUsername("butaji9l")
        .authorities("USER")
        .passwordEncoder(passwordEncoder()::encode)
        .password("1234")
        .build();
    manager.createUser(user2);

    final var user3 = User
        .withUsername("TamTen")
        .authorities("USER")
        .passwordEncoder(passwordEncoder()::encode)
        .password("1234")
        .build();
    manager.createUser(user3);

    final var user4 = User
        .withUsername("Gonduras")
        .authorities("USER")
        .passwordEncoder(passwordEncoder()::encode)
        .password("1234")
        .build();
    manager.createUser(user4);

    return manager;
  }

  protected JwtAuthenticationConverter authenticationConverter() {
    final var authoritiesConverter = new JwtGrantedAuthoritiesConverter();
    authoritiesConverter.setAuthorityPrefix("SCOPE_");
    authoritiesConverter.setAuthoritiesClaimName(AUTHORITIES_CLAIM_NAME);

    final var converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
    return converter;
  }

  @Bean
  SimpleUrlAuthenticationFailureHandler failureHandler() {
    return new SimpleUrlAuthenticationFailureHandler("http://localhost:8080/error");
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .cors()
        .and()
        .csrf().disable()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .authorizeRequests(configurer ->
            configurer
                .antMatchers(
                    API_URL + "/error",
                    API_URL + "/login",
                    API_URL + "/register"
                )
                .permitAll()
                .anyRequest()
                .authenticated()
        )
        .oauth2ResourceServer()
        .jwt()
        .jwtAuthenticationConverter(authenticationConverter());


    http.exceptionHandling()
        .authenticationEntryPoint(securityProblemSupport)
        .accessDeniedHandler(securityProblemSupport);
  }

  @Bean
  public KeyStore keyStore() {
    try {
      final var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
      final var resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(keyStorePath);
      keyStore.load(resourceAsStream, keyStorePassword.toCharArray());
      return keyStore;
    } catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
      log.error("Unable to load keystore: {}", keyStorePath, e);
    }

    throw new IllegalArgumentException("Unable to load keystore");
  }

  @Bean
  public RSAPrivateKey jwtSigningKey(KeyStore keyStore) {
    try {
      final var key = keyStore.getKey(keyAlias, privateKeyPassphrase.toCharArray());
      if (key instanceof RSAPrivateKey) {
        return (RSAPrivateKey) key;
      }
    } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
      log.error("Unable to load private key from keystore: {}", keyStorePath, e);
    }

    throw new IllegalArgumentException("Unable to load private key");
  }

  @Bean
  public RSAPublicKey jwtValidationKey(KeyStore keyStore) {
    try {
      final var certificate = keyStore.getCertificate(keyAlias);
      final var publicKey = certificate.getPublicKey();

      if (publicKey instanceof RSAPublicKey) {
        return (RSAPublicKey) publicKey;
      }
    } catch (KeyStoreException e) {
      log.error("Unable to load private key from keystore: {}", keyStorePath, e);
    }

    throw new IllegalArgumentException("Unable to load RSA public key");
  }
  @Override
  public void configure(WebSecurity web) {
    web.ignoring().antMatchers("/swagger-ui/**", "/v3/api-docs/**");
  }


  @Bean
  public JwtDecoder jwtDecoder(RSAPublicKey rsaPublicKey) {
    return NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();
  }
}
