package ru.micord.ervu.security.listener;

import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.DeferredCsrfToken;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import static org.springframework.web.context.request.RequestAttributes.REFERENCE_REQUEST;


@Component
public class JwtUpdateListener implements ApplicationListener<AuthenticationSuccessEvent> {
  private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  private final CsrfTokenRepository tokenRepository;
  private final Set<AntPathRequestMatcher> csrfUpdateRequiredPathMatchers;

  private CsrfTokenRequestHandler requestHandler = new CsrfTokenRequestAttributeHandler();

  @Autowired
  public JwtUpdateListener(CsrfTokenRepository tokenRepository) {
    Assert.notNull(tokenRepository, "tokenRepository cannot be null");
    this.tokenRepository = tokenRepository;
    this.csrfUpdateRequiredPathMatchers = Arrays.stream(new String[] {"/esia/auth"})
        .map(AntPathRequestMatcher::new)
        .collect(Collectors.toSet());
  }

  @Override
  public void onApplicationEvent(AuthenticationSuccessEvent event) {
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = (HttpServletRequest) Objects.requireNonNull(requestAttributes)
        .resolveReference(
            REFERENCE_REQUEST);
    HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();

    //if csrf cookie update is not required return
    if (this.csrfUpdateRequiredPathMatchers.stream()
        .noneMatch(matcher -> matcher.matches(request))) {
      return;
    }
    boolean containsToken = this.tokenRepository.loadToken(request) != null;

    if (containsToken) {
      this.tokenRepository.saveToken(null, request, response);
      DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request,
          response
      );
      this.requestHandler.handle(request, response, deferredCsrfToken::get);
      this.logger.debug("Replaced CSRF Token");
    }
  }
}
