package com.jd.lightapplication.security;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.jd.lightapplication.common.exception.CaptchaException;
import com.jd.lightapplication.common.lang.R;
import com.jd.lightapplication.common.lang.WeChatConfig;
import com.jd.lightapplication.easemob.EasemobComtroller;
import com.jd.lightapplication.easemob.dto.EasemobUserAddDto;
import com.jd.lightapplication.model.SysRole;
import com.jd.lightapplication.model.SysUser;
import com.jd.lightapplication.model.SysUserRole;
import com.jd.lightapplication.service.SysRoleService;
import com.jd.lightapplication.service.SysUserRoleService;
import com.jd.lightapplication.service.SysUserService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Data
@AllArgsConstructor
@Component
@Slf4j
public class MobileLoginFilter extends OncePerRequestFilter implements InitializingBean {

    private SysUserService sysUserService;
    private SysRoleService sysRoleService;
    private SysUserRoleService sysUserRoleService;

    private EasemobComtroller easemobComtroller;

    LoginFailureHandler loginFailureHandler;
    private SecurityProperties securityProperties;
    private AuthenticationFailureHandler authenticationFailureHandler;
    private RedisTemplate redisTemplate;

    private final String CAPTCHA = "CAPTCHA";
    private final String QUICK = "QUICK";

    private WeChatConfig weChatConfig;

    private final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
    private final String PHONE_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {

        String url = request.getRequestURI();

        if (url.equals("/login/mobile") && request.getMethod().equals("POST")) {
            try {
                validate(request);
            } catch (AuthenticationException e) {
                //验证出现异常，使用自定义的失败处理器来处理，并且直接return，不执行后面的过滤器
                loginFailureHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }
        filterChain.doFilter(request, response);
    }

    /**
     * 短信验证码的验证逻辑
     *
     * @param request
     */
    private void validate(HttpServletRequest request) {

        String phone = request.getParameter("phone");
        String code = request.getParameter("code");
        String type = request.getParameter("type");
        System.err.println("【phone:" + phone + "】【code:" + code + "】【type:" + type + "】");

        if (QUICK.equals(type)) {//表示手机快捷登录 用code去微信服务器换取手机号
            phone = this.getPhone(code);
            //快捷登录没有手机号信息，需要把手机号存到request里面。让后面的过滤器能取到手机号
            request.setAttribute("phone", phone);
        } else if (CAPTCHA.equals(type)) {//表示手机验证码登录
            Object redisCode = redisTemplate.opsForValue().get(phone);
            if (StringUtils.isBlank(phone) || StringUtils.isBlank(code) || redisCode == null || !code.equals(redisCode.toString())) {
                throw new CaptchaException("验证码错误");
            }
        }

        this.handlePhone(phone);

        //把验证码从redis里面移除  让验证码失效
        redisTemplate.delete(phone);
    }

    /**
     * 处理手机号 如果用户表存在phone则不做处理，如果不存在phone则新增用户信息默认角色为来宾
     *
     * @param phone
     */
    private void handlePhone(String phone) {
        SysUser sysUser = sysUserService.getByPhone(phone);
        if (sysUser != null) {
            return;
        }
        //先添加用户信息
        sysUser = new SysUser()
                .setPhone(phone)
                .setStatu(1)//表示可用
                .setCreated(DateUtil.now());
        Boolean result = sysUserService.save(sysUser);
        //再添加用户角色
        LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysRole::getCode, "GUEST");
        SysRole sysRole = sysRoleService.getOne(wrapper);
        SysUserRole sysUserRole = new SysUserRole()
                .setRoleId(sysRole.getId())
                .setUserId(sysUser.getId());
        Boolean result1 = sysUserRoleService.save(sysUserRole);

        if (!(result && result1)) {
            throw new CaptchaException("系统异常，请联系管理员！");
        }
        log.debug("手机号登录未找到用户信息，" + (result ? "新增用户成功。" : "新增用户失败。") + "phone=" + phone);
        log.debug("手机号登录未找到用户信息，" + (result1 ? "新增角色成功。" : "新增角色失败。") + "phone=" + phone);

        //向环信应用注册用户
        EasemobUserAddDto easemobUserAddDto = new EasemobUserAddDto()
                .setUsername(sysUser.getPhone())
                .setPassword(Base64.encode(sysUser.getPhone()));
        JSONObject jsonObject = (JSONObject) easemobComtroller.registerUser(easemobUserAddDto).getData();
        String error = jsonObject.getStr("error");
        if (error != null) {
            log.warn("环信用户注册失败，错误信息：" + error + "。用户信息" + sysUser);
        } else {
            log.info("环信用户注册成功" + sysUser);
        }
    }

    /**
     * 根据小程序拿到的动态令牌获取用户手机号
     *
     * @param code
     * @return
     */
    public String getPhone(String code) {
        //先获取accessToken
        String accessToken = this.getAccessToken();
        //拿accessToken和动态令牌去微信获取用户手机号信息
        JSONObject jsonObject = JSONUtil.createObj();
        jsonObject.set("code", code);
        String phoneUrl = String.format(PHONE_URL, accessToken);
        String phoneResult = HttpUtil.createPost(phoneUrl)
                .header("Content-Type", "application/json")
                .body(jsonObject.toString())
                .execute()
                .body();
        JSONObject result = JSONUtil.parseObj(phoneResult);
        if (!"ok".equals(result.get("errmsg"))) {
            return null;
        }
        JSONObject phoneInfo = result.getJSONObject("phone_info");
        return phoneInfo.getStr("phoneNumber");
    }

    /**
     * 获取accessToken
     *
     * @return
     */
    private String getAccessToken() {
        //拼接access_token请求地址
        String accessTokenUrl = String.format(ACCESS_TOKEN_URL, weChatConfig.getAppId(), weChatConfig.getAppSecret());
        //先获取access_token
        String accessTokenResult = HttpUtil.createGet(accessTokenUrl)
                .execute()
                .charset("UTF-8")
                .body();
        JSONObject result = JSONUtil.parseObj(accessTokenResult);
        String accessToken = result.get("access_token").toString();
        return accessToken;
    }
}


