package ru.micord.ervu.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
import org.springframework.web.filter.RequestContextFilter;
import ru.micord.ervu.security.esia.service.EsiaAuthService;
import ru.micord.ervu.security.filter.FilterChainExceptionHandler;
import ru.micord.ervu.security.webbpm.jwt.JwtAuthenticationProvider;
import ru.micord.ervu.security.webbpm.jwt.JwtMatcher;
import ru.micord.ervu.security.webbpm.jwt.UnauthorizedEntryPoint;
import ru.micord.ervu.security.webbpm.jwt.filter.JwtAuthenticationFilter;
import ru.micord.ervu.security.webbpm.jwt.helper.SecurityHelper;

import static ru.micord.ervu.security.SecurityConstants.ESIA_LOGOUT;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
  private static final String[] PERMIT_ALL = new String[] {
      "/version", "/esia/url", "/esia/auth", "esia/refresh", "/esia/logout"
  };
  @Autowired
  private JwtAuthenticationFilter jwtAuthenticationFilter;
  @Autowired
  private EsiaAuthService esiaAuthService;
  @Autowired
  private FilterChainExceptionHandler filterChainExceptionHandler;
  @Autowired
  private JwtAuthenticationProvider jwtAuthenticationProvider;

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) {
    auth.authenticationProvider(jwtAuthenticationProvider);
  }

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http,
                                         CookieCsrfTokenRepository tokenRepository,
                                         UnauthorizedEntryPoint entryPoint)
      throws Exception {
    XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
    delegate.setCsrfRequestAttributeName(null);
    // Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
    // default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
    CsrfTokenRequestHandler requestHandler = delegate::handle;
    http.authorizeHttpRequests(
            (authorizeHttpRequests) -> authorizeHttpRequests.requestMatchers(PERMIT_ALL)
                .permitAll()
                .anyRequest()
                .authenticated())
        .csrf((csrf) -> csrf.csrfTokenRepository(tokenRepository)
            .csrfTokenRequestHandler(requestHandler)
            .sessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy()))
        .logout((logout) -> logout.logoutUrl(ESIA_LOGOUT)
            .logoutSuccessHandler(new LogoutSuccessHandler(tokenRepository, esiaAuthService)))
        .exceptionHandling()
        .authenticationEntryPoint(entryPoint)
        .accessDeniedHandler(new AccessDeniedHandlerImpl())
        .and()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    http.addFilterBefore(new RequestContextFilter(), LogoutFilter.class);
    http.addFilterAfter(filterChainExceptionHandler, RequestContextFilter.class);
    return http.build();
  }

  @Bean
  public CookieCsrfTokenRepository cookieCsrfTokenRepository() {
    CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
    tokenRepository.setCookieName(TokenConstants.CSRF_TOKEN_NAME);
    tokenRepository.setHeaderName(TokenConstants.CSRF_HEADER_NAME);
    tokenRepository.setCookiePath("/");
    return tokenRepository;
  }

  @Bean
  public UnauthorizedEntryPoint entryPoint(SecurityHelper securityHelper) {
    return new UnauthorizedEntryPoint(securityHelper);
  }

  @Bean
  AuthenticationManager authenticationManager(
      AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
  }

  @Bean
  public SecurityHelper securityHelper() {
    return new SecurityHelper();
  }

  @Bean
  public JwtAuthenticationFilter jwtAuthenticationFilter(SecurityHelper securityHelper,
                                                         AuthenticationManager manager,
                                                         UnauthorizedEntryPoint entryPoint) {
    JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(
        new JwtMatcher("/**", PERMIT_ALL), entryPoint, securityHelper);
    jwtAuthenticationFilter.setAuthenticationManager(manager);
    return jwtAuthenticationFilter;
  }
}
