package com.ffii.core.config.filter;

import java.io.IOException;
import java.util.function.Supplier;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.servlet.LocaleResolver;

import com.ffii.core.common.mobile.service.AccessTokenService;
import com.ffii.core.user.entity.User;
import com.ffii.core.user.service.UserService;
import com.ffii.core.utils.LocaleUtils;

/** @author Fung */
// @Component
public class TokenAuthFilter extends GenericFilterBean {

  protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

  private final String AUTH_HEADER_DEVICE_ID;
  private final String AUTH_HEADER_ACCESS_TOKEN;
  private AccessTokenService accessTokenService;
  private UserService userService;
  private LocaleResolver localeResolver;

  public TokenAuthFilter(
      @Value("${app.mobile.auth-header.deviceId:X-ATT-DeviceId}") String authHeaderDeviceId,
      @Value("${app.mobile.auth-header.accessToken:X-ATT-Access-Token}") String authHeaderAccessToken,
      AccessTokenService accessTokenService,
      UserService userService,
      LocaleResolver localeResolver) {
    this.AUTH_HEADER_DEVICE_ID = authHeaderDeviceId;
    this.AUTH_HEADER_ACCESS_TOKEN = authHeaderAccessToken;
    this.accessTokenService = accessTokenService;
    this.userService = userService;
    this.localeResolver = localeResolver;
  }

  @Override
  public void doFilter(
      ServletRequest request,
      ServletResponse response,
      FilterChain chain) throws IOException, ServletException {

    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    String deviceId = httpRequest.getHeader(this.AUTH_HEADER_DEVICE_ID);
    String accessToken = httpRequest.getHeader(this.AUTH_HEADER_ACCESS_TOKEN);

    if (deviceId != null && accessToken != null) {
      try {
        Supplier<BadCredentialsException> badCredentials = () -> new BadCredentialsException(messages.getMessage(
            "AbstractUserDetailsAuthenticationProvider.badCredentials",
            "Bad credentials"));

        User user = userService
            .loadUserByUsername(accessTokenService.findByDeviceIdAndAccessToken(deviceId, accessToken)
                .orElseThrow(badCredentials)
                .getUsername());

        if (user.isAccountNonLocked() && user.isAccountNonExpired() && user.isCredentialsNonExpired() && user.isEnabled()) {
          Authentication authResult = new PreAuthenticatedAuthenticationToken(user, null,
              user.getAuthorities());

          SecurityContextHolder.getContext().setAuthentication(authResult);
          // success
          if (StringUtils.isNotBlank(user.getLocale()))
            localeResolver.setLocale(httpRequest, httpResponse, LocaleUtils.from(user.getLocale()));
          chain.doFilter(request, response);
          SecurityContextHolder.getContext().setAuthentication(null);
          SecurityContextHolder.clearContext();
        } else {
          throw badCredentials.get();
        }

      } catch (AuthenticationException failed) {
        SecurityContextHolder.getContext().setAuthentication(null);
        SecurityContextHolder.clearContext();

        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Authentication request for failed!", failed);
        }
        chain.doFilter(request, response);
      }
    } else {
      chain.doFilter(request, response);
    }
  }
}
