package cn.bunny.security.filter;


import cn.bunny.common.service.utils.ResponseUtil;
import cn.bunny.dto.user.LoginDto;
import cn.bunny.result.Result;
import cn.bunny.result.ResultCodeEnum;
import cn.bunny.result.constant.RedisUserConstant;
import cn.bunny.security.handelr.SecurityAuthenticationFailureHandler;
import cn.bunny.security.handelr.SecurityAuthenticationSuccessHandler;
import cn.bunny.security.service.CustomUserDetailsService;
import cn.bunny.vo.system.login.LoginVo;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * * UsernamePasswordAuthenticationFilter
 * * 也可以在这里添加验证码、短信等的验证
 * 由于SpringSecurity的登录只能是表单形式 并且用户名密码需要时username、password,可以通过继承 UsernamePasswordAuthenticationFilter 获取登录请求的参数
 * 再去设置到 UsernamePasswordAuthenticationToken 中 来改变请求传参方式、参数名等 或者也可以在登录的时候加入其他参数等等
 */
public class TokenLoginFilterService extends UsernamePasswordAuthenticationFilter {
    private final RedisTemplate<String, Object> redisTemplate;
    private final CustomUserDetailsService customUserDetailsService;
    private LoginDto loginDto;

    // 依赖注入
    public TokenLoginFilterService(AuthenticationConfiguration authenticationConfiguration, RedisTemplate<String, Object> redisTemplate, CustomUserDetailsService customUserDetailsService) throws Exception {
        this.setAuthenticationSuccessHandler(new SecurityAuthenticationSuccessHandler());
        this.setAuthenticationFailureHandler(new SecurityAuthenticationFailureHandler());
        this.setPostOnly(false);
        // ? 指定登录接口及提交方式，可以指定任意路径
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/*/login", HttpMethod.POST.name()));
        this.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());
        // 依赖注入
        this.redisTemplate = redisTemplate;
        this.customUserDetailsService = customUserDetailsService;
    }

    /**
     * * 登录认证，获取输入的用户名和密码，调用方法认证
     * 接受前端login登录参数
     * 在这里可以设置短信验证登录
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            // 获取用户信息
            loginDto = new ObjectMapper().readValue(request.getInputStream(), LoginDto.class);

            // 登录验证码判断
            String username = loginDto.getUsername();
            String emailCode = loginDto.getEmailCode().toLowerCase();
            String redisEmailCode = (String) redisTemplate.opsForValue().get(RedisUserConstant.getAdminUserEmailCodePrefix(username));

            // 如果不存在验证码
            if (!StringUtils.hasText(emailCode)) {
                ResponseUtil.out(response, Result.error(ResultCodeEnum.EMAIL_CODE_NOT_EMPTY));
                return null;
            }
            if (!StringUtils.hasText(redisEmailCode)) {
                ResponseUtil.out(response, Result.error(ResultCodeEnum.SEND_EMAIL_CODE_NOT_EMPTY));
                return null;
            }
            // 验证码不匹配
            if (!Objects.equals(redisEmailCode.toLowerCase(), emailCode)) {
                ResponseUtil.out(response, Result.error(ResultCodeEnum.EMAIL_CODE_NOT_MATCHING));
                return null;
            }

            // 封装对象，将用户名密码传入
            Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
            return this.getAuthenticationManager().authenticate(authenticationToken);
        } catch (IOException e) {
            throw new RuntimeException(e.getLocalizedMessage());
        }
    }

    /**
     * * 认证成功调用方法
     * 返回登录成功后的信息
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) {
        // 封装返回对象
        LoginVo loginVo = customUserDetailsService.login(loginDto);

        // 判断用户是否被锁定
        if (loginVo.getStatus() == 1) {
            ResponseUtil.out(response, Result.error(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED));
            return;
        }

        // 将值存入Redis中
        redisTemplate.opsForValue().set(RedisUserConstant.getAdminLoginInfoPrefix(loginVo.getEmail()), loginVo, 15, TimeUnit.DAYS);
        // 将Redis中验证码删除
        redisTemplate.delete(RedisUserConstant.getAdminUserEmailCodePrefix(loginVo.getEmail()));
        // 返回登录信息
        ResponseUtil.out(response, Result.success(loginVo));
    }

    /**
     * * 认证失败调用方法，失败判断
     * 1. 是否包含用户名
     * 2. 是否包含密码
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
        // 账号和密码不能为空
        if (loginDto == null) {
            ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_ERROR_USERNAME_PASSWORD_NOT_EMPTY));
        }
        // 用户名为空
        if (!StringUtils.hasText(loginDto.getUsername())) {
            ResponseUtil.out(response, Result.error(ResultCodeEnum.USERNAME_NOT_EMPTY));
        }
        // 密码为空
        if (!StringUtils.hasText(loginDto.getPassword())) {
            ResponseUtil.out(response, Result.error(ResultCodeEnum.PASSWORD_NOT_EMPTY));
        }
        // 抛出异常，账号或密码错误
        ResponseUtil.out(response, Result.error(null, ResultCodeEnum.LOGIN_ERROR));
    }
}
