package com.njzscloud.common.security.support;

import com.njzscloud.common.security.handler.LoginPostHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.web.filter.GenericFilterBean;

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 java.io.IOException;
import java.util.Collection;
import java.util.Optional;

/**
 * 认证过滤器
 */
@Slf4j
@RequiredArgsConstructor
public class CombineAuthenticationFilter extends GenericFilterBean {
    /**
     * 支持的登录方式列表
     */
    private final Collection<LoginPreparer> loginPreparers;

    /**
     * 认证管理器{@link AbstractAuthenticationProvider}
     */
    private final AuthenticationManager authenticationManager;

    /**
     * 登陆后置处理器
     */
    private final LoginPostHandler loginPostHandler;

    /**
     * TOKEN 存取器
     */
    private final SecurityContextRepository securityContextRepository;


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        Optional<LoginPreparer> loginPreparerOptional = loginPreparers.stream().filter(it -> it.support(request)).findFirst();

        if (!loginPreparerOptional.isPresent()) {
            filterChain.doFilter(request, response);
            return;
        }

        LoginPreparer loginPreparer = loginPreparerOptional.get();

        LoginForm loginForm;
        try {
            loginForm = loginPreparer.createLoginForm(request);
        } catch (AuthenticationException failed) {
            unsuccessfulAuthentication(request, response, failed);
            return;
        } catch (Exception e) {
            unsuccessfulAuthentication(request, response, new InternalAuthenticationServiceException("登录表单解析失败", e));
            return;
        }

        try {
            Authentication authentication = createAuthenticationToken(request, loginForm);
            // 开始认证
            Authentication authenticationResult = authenticationManager.authenticate(authentication);
            // 不为空 则认证成功
            if (authenticationResult != null) {
                successfulAuthentication(request, response, authenticationResult);
            }
        } catch (AuthenticationException failed) {
            unsuccessfulAuthentication(request, response, failed);
        }
    }

    /**
     * 构建待认证对象
     *
     * @param request HTTP 请求对象
     * @return Authentication 待认证对象
     */
    private Authentication createAuthenticationToken(HttpServletRequest request, LoginForm loginForm) {
        // 额外登录信息
        AuthenticationDetails details = AuthenticationDetails.create(request);

        return UserAuthenticationToken.create(loginForm, details);
    }


    /**
     * 登录成功
     *
     * @param request        HTTP 请求对象
     * @param response       HTTP 响应对象
     * @param authentication 已认证的认证对象
     */
    private void successfulAuthentication(HttpServletRequest request,
                                          HttpServletResponse response,
                                          Authentication authentication) throws IOException, ServletException {
        // 保存认证结果
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);
        SecurityContextHolder.setContext(context);

        // securityContextRepository.saveContext(context, request, response);

        loginPostHandler.recordLoginHistory(authentication);

        loginPostHandler.onAuthenticationSuccess(request, response, authentication);

    }

    /**
     * 登录失败
     *
     * @param request  HTTP 请求对象
     * @param response HTTP 响应对象
     * @param failed   异常对象
     */
    private void unsuccessfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            AuthenticationException failed) throws IOException, ServletException {
        // 清空认证信息
        SecurityContextHolder.clearContext();

        // 认证失败后的处理
        loginPostHandler.onAuthenticationFailure(request, response, failed);
    }
}
