package fastweb.filter;

import com.google.gson.Gson;

import fastweb.config.JwtUtil;
import fastweb.config.TokenVO;
import fastweb.domain.system.WrapperResult;
import fastweb.domain.usertype.User;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;


/**
 * 登录拦截器
 * <p>
 * 登录地址可以在SecurityConfig中配置
 * </p>
 *
 * @author Gerry
 */
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {
    private final StringRedisTemplate stringRedisTemplate;


    private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    public JwtLoginFilter(String url, AuthenticationManager authManager, StringRedisTemplate stringRedisTemplate) {
        super(new AntPathRequestMatcher(url));
        setAuthenticationManager(authManager);
        this.stringRedisTemplate = stringRedisTemplate;
    }
    /**
     * 尝试认证（从request中取用户名和密码，生成认证实体）
     *
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return Authentication（AuthenticationProvider会对其进行处理）
     * @throws AuthenticationException 认证异常
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // 返回一个验证令牌
        return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(username, password));
    }
    /**
     * 认证成功返回统一格式的JSON
     *
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @param chain    FilterChain
     * @param auth     Authentication
     * @throws IOException IO异常
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException {
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_OK);
        response.setCharacterEncoding("UTF-8");
        User sysUser = (User) auth.getDetails();
        String requestURI = request.getRequestURI();
        String type = request.getParameter("type");
        Gson gson = new Gson();
        String token;
        try {
            Map<String, Object> payload = new HashMap<>();
            payload.put("username", auth.getName());
            token = JwtUtil.genToken(payload);
        } catch (Exception e) {
            response.getWriter().print(gson.toJson(WrapperResult.failedWrappedResult("Token生成失败")));
            return;
        }

        TokenVO tokenVO = new TokenVO();
        tokenVO.setToken(token);
        tokenVO.setUsername(auth.getName());
        tokenVO.setType(sysUser.getType()==null?"0":sysUser.getType().toString());
        Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
        List<String> authorityList = new ArrayList<>();
        authorities.forEach(o -> {
            authorityList.add(o.getAuthority());
        });
        if (auth.getDetails() instanceof User) {
            tokenVO.setId(sysUser.getId());


            if (stringRedisTemplate.hasKey(sysUser.getId())) {
                String jwt = stringRedisTemplate.boundValueOps(sysUser.getId()).get();
                if (jwt != null && stringRedisTemplate.hasKey(jwt)) {
                    stringRedisTemplate.delete(jwt);
                }
                stringRedisTemplate.delete(sysUser.getId());
                stringRedisTemplate.delete(sysUser.getId()+"_"+type);
            }
            stringRedisTemplate
                    .boundValueOps(sysUser.getId())
                    .set(token);

           //区分app和pc端
            String typeId=sysUser.getId()+"_"+ type;
            stringRedisTemplate
                    .boundValueOps(typeId)
                    .set(token);
            BoundSetOperations<String, String> setOperations = stringRedisTemplate.boundSetOps(token);
            if (!authorities.isEmpty()) {
                setOperations.add(authorityList.toArray(new String[]{}));
            }else{
                setOperations.add(sysUser.getId());
            }
            stringRedisTemplate
                    .expire(sysUser.getId(), JwtUtil.EXP_TIME, TimeUnit.MINUTES);
            stringRedisTemplate
                    .expire(typeId, JwtUtil.EXP_TIME, TimeUnit.MINUTES);
            stringRedisTemplate
                    .expire(token, JwtUtil.EXP_TIME, TimeUnit.MINUTES);
        }
        response.getWriter().print(gson.toJson(WrapperResult.successWrapedResult(tokenVO)));
    }


    /**
     * 认证失败返回统一格式的JSON
     *
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @param failed   AuthenticationException
     * @throws IOException IO异常
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_OK);
        response.setCharacterEncoding("UTF-8");
        Gson gson = new Gson();
        WrapperResult wrappedResult = WrapperResult.failedWrappedResult(failed.getMessage());
        wrappedResult.setResultValue(null);
        response.getWriter().print(gson.toJson(wrappedResult));
    }
}
