package com.muchinfo.mtp3century.filter;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.muchinfo.mtp3century.utils.CenturyDecryptUtil;
import com.muchinfo.mtp3century.utils.RedisServiceUtil;
import com.muchinfo.mtp3century.utils.ResponseUtils;
import com.muchinfo.mtp3century.utils.SpringBeanUtils;
import com.muchinfo.mtp3common.enumtype.Constants;
import com.muchinfo.mtp3common.enumtype.MessageType;
import com.muchinfo.mtp3common.enumtype.SysConstants;
import com.muchinfo.mtp3common.enumtype.SysEnums;
import com.muchinfo.mtp3common.utils.*;
import com.muchinfo.mtp3interface.mapper.ISystemmanagerMapper;
import com.muchinfo.mtp3pojos.entity.Systemmanager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
        setAuthenticationManager(authenticationManager);
    }

    /**
     * 校验账号密码
     *
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
        BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
        StringBuilder strBuilder = new StringBuilder();
        String inputStr;
        while ((inputStr = streamReader.readLine()) != null) strBuilder.append(inputStr);
        Systemmanager sysUser = JsonUtils.toBean(CenturyDecryptUtil.decryptForJson(strBuilder.toString(), request), Systemmanager.class);
        assert sysUser != null;
        String username = sysUser.getUsername();
        username = username != null ? username : "";
        String password = sysUser.getPassword();
        password = password != null ? password : "";
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        setDetails(request, authRequest);
        return getAuthenticationManager().authenticate(authRequest);
    }

    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    /**
     * 认证成功所执行的方法
     *
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
        StringBuilder sb = new StringBuilder();
        for (GrantedAuthority authority : authorities) {
            sb.append(authority.getAuthority()).append(",");
        }
        String token = JwtUtil.TOKEN_PREFIX + JwtUtil.generateToken(authResult.getName(), sb.toString());
        // 将token添加到header中
        response.setHeader(JwtUtil.TOKEN_HEADER, token);
        ResponseUtils.writeSucceed(response, HttpServletResponse.SC_OK, MessageType.MESSAGE_CODE_SYSTEM010.getCode(), request.getHeader("Accept-Language"), token);
        log.info("登录成功，username: {}, token: {}", authResult.getName(), token);
    }


    /**
     * 认证失败所执行的方法
     *
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
        Object cacheObject = SpringBeanUtils.getBean(RedisServiceUtil.class).getCacheObject("user-login-fail");
        log.error("AuthenticationException", failed);
        String[] param = new String[4];
        //失败超过默认次数锁定用户
        Systemmanager sysUser = SpringBeanUtils.getBean(ISystemmanagerMapper.class).selectOne(new QueryWrapper<Systemmanager>().eq("LOGINCODE", cacheObject));
        if (Objects.nonNull(sysUser)) {
            sysUser.setLoginfailnum(sysUser.getLoginfailnum() + 1);
            Object redisParam = SpringBeanUtils.getBean(RedisServiceUtil.class).getCacheMapValue(Constants.REDIS_HEADER_SYSPARAM,
                    SysConstants.SYSPARAM_CODE_PWERR_TIMES);
            Object errorCountTime = SpringBeanUtils.getBean(RedisServiceUtil.class).getCacheMapValue(Constants.REDIS_HEADER_SYSPARAM,
                    SysConstants.SYSPARAM_CODE_LOCKTIME);
            double defaultMin = errorCountTime == null ? 180d : Double.parseDouble(String.valueOf(errorCountTime));
            int errorNum = redisParam == null ? 3 : Integer.parseInt(String.valueOf(redisParam));
            //设置了= 0不限制 错误次数
            if (errorNum > 0) {
                param[0] = String.valueOf(errorNum - sysUser.getLoginfailnum());
                param[1] = String.valueOf(errorNum);
                param[2] = String.valueOf(defaultMin);
                param[3] = ".";
                // 失败3次锁定
                if (sysUser.getLoginfailnum() >= errorNum) {
                    sysUser.setLogintime(new Date());
                    sysUser.setManagerstatus(SysEnums.SysStatus.lock.getCode());
                    param[3] = "," + Constants.userlock_msg;
                }
            }
            SpringBeanUtils.getBean(ISystemmanagerMapper.class).updateById(sysUser);
        }

        SpringBeanUtils.getBean(RedisServiceUtil.class).deleteByPrex("user-login-fail");
        //清理上下文
        SecurityContextHolder.clearContext();
        //判断异常类
        if (failed instanceof InternalAuthenticationServiceException) {
            ResponseUtils.writeFiled(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageType.MESSAGE_ERROR_CODE_SYSTEM009.getCode(), request.getHeader("Accept-Language"));
        } else if (failed instanceof UsernameNotFoundException) {
            ResponseUtils.writeFiled(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageType.MESSAGE_ERROR_CODE_SYSTEM010.getCode(), request.getHeader("Accept-Language"));
        } else if (failed instanceof BadCredentialsException) {
            if (param[0] == null) {
                ResponseUtils.writeFiled(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageType.MESSAGE_ERROR_CODE_SYSTEM011.getCode(), request.getHeader("Accept-Language"));
            } else {
                ResponseUtils.writeFiledForParameters(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, param, request.getHeader("Accept-Language"));
            }
        } else if (failed instanceof LockedException) {
            ResponseUtils.writeFiled(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageType.MESSAGE_ERROR_CODE_SYSTEM012.getCode(), request.getHeader("Accept-Language"));
        } else if (failed instanceof CredentialsExpiredException) {
            ResponseUtils.writeFiled(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageType.MESSAGE_ERROR_CODE_SYSTEM013.getCode(), request.getHeader("Accept-Language"));
        } else if (failed instanceof DisabledException) {
            ResponseUtils.writeFiled(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, MessageType.MESSAGE_ERROR_CODE_SYSTEM014.getCode(), request.getHeader("Accept-Language"));
        }
    }
}
