
package com.tmerclub.cloud.auth.controller;

import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.tmerclub.cloud.api.auth.bo.LoginInfoBO;
import com.tmerclub.cloud.api.auth.bo.UidInfoBO;
import com.tmerclub.cloud.api.auth.constant.LoginType;
import com.tmerclub.cloud.api.auth.constant.SocialType;
import com.tmerclub.cloud.api.auth.constant.SysTypeEnum;
import com.tmerclub.cloud.api.auth.vo.TokenInfoVO;
import com.tmerclub.cloud.api.biz.feign.NotifyFeignClient;
import com.tmerclub.cloud.api.user.bo.UidAndUserIdBO;
import com.tmerclub.cloud.api.user.dto.UserRegisterDTO;
import com.tmerclub.cloud.api.user.feign.UserFeignClient;
import com.tmerclub.cloud.auth.config.WxConfig;
import com.tmerclub.cloud.auth.dto.*;
import com.tmerclub.cloud.auth.manager.TokenStore;
import com.tmerclub.cloud.auth.mapper.AuthSocialMapper;
import com.tmerclub.cloud.auth.model.AuthSocial;
import com.tmerclub.cloud.auth.service.AuthAccountService;
import com.tmerclub.cloud.auth.service.AuthSocialService;
import com.tmerclub.cloud.common.cache.constant.OauthCacheNames;
import com.tmerclub.cloud.common.cache.util.RedisUtil;
import com.tmerclub.cloud.common.constant.SendTypeEnum;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.security.AuthUserContext;
import com.tmerclub.cloud.common.security.adapter.AuthConfigAdapter;
import com.tmerclub.cloud.rbac.service.MenuPermissionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;

/**
 * @author tmerclub
 * @date 2020/6/30
 */
@RestController
@Tag(name = "登录")
public class LoginController {

    @Autowired
    private CaptchaService captchaService;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private AuthAccountService authAccountService;
    @Autowired
    private MenuPermissionService menuPermissionService;
    @Autowired
    private AuthSocialService authSocialService;
    @Autowired
    private WxConfig wxConfig;
    @Autowired
    private AuthSocialMapper authSocialMapper;
    @DubboReference
    private NotifyFeignClient notifyFeignClient;
    @DubboReference
    private UserFeignClient userFeignClient;

    @Value("${sa-token.token-name}")
    private String tokenName;



    @PostMapping("/ua/login")
    @Operation(summary = "账号密码(用于h5、pc登录)", description = "通过账号/手机号/用户名密码登录，还要携带用户的类型，也就是用户所在的系统")
    public ServerResponseEntity<TokenInfoVO> login(
            @Valid @RequestBody AuthenticationDTO authenticationDTO) {
        if (!Objects.equals(authenticationDTO.getSysType(), SysTypeEnum.ORDINARY.value())) {
            return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
        }
        return doLogin(authenticationDTO, LoginType.PASSWORD.value());
    }

    @PostMapping("/ua/loginByCode")
    @Operation(summary = "手机号+验证码登录(用于前端登录)", description = "通过手机号+验证码登录，还要携带用户的类型，也就是用户所在的系统")
    public ServerResponseEntity<TokenInfoVO> loginWithCode(@Valid @RequestBody LoginCodeDTO loginCode) {
        // 该接口是用户端在（pc、h5、小程序）使用
        if (!Objects.equals(loginCode.getSysType(), SysTypeEnum.ORDINARY.value())) {
            return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
        }
        boolean flag = (Objects.equals(loginCode.getSocialType(), SocialType.MA.value())
                || Objects.equals(loginCode.getSocialType(), SocialType.MP.value()));
        if (StrUtil.isBlank(loginCode.getTempUid())
                && flag) {
            return ServerResponseEntity.showFailMsg("tempUid is blank");
        }
//        //TODO 校验验证码
//        ServerResponseEntity<Boolean> validCodeResponse = notifyFeignClient.checkValidCode(loginCode.getMobile(), loginCode.getValidCode(), SendTypeEnum.LOGIN);
//        if (!validCodeResponse.isSuccess() || !validCodeResponse.getData()) {
//            return ServerResponseEntity.fail(ResponseEnum.VERIFICATION_CODE_ERROR);
//        }
        // 如果没有注册的话，短信登录将会进行注册
        // 在pc/小程序/公众号的登录，都有短信登录的方法。但是公众号/小程序的短信登录，登录了之后会见这个用户和公众号/小程序绑定一起（登录并绑定）
        return threeInOneLogin(loginCode.getMobile(), loginCode.getTempUid(), loginCode.getSocialType());
    }

    @PostMapping("/ua/wx/login")
    @Operation(summary = "账号密码登录(用于微信小程序和公众号)", description = "二合一登录（包含绑定+登录），一定要传tempUid")
    public ServerResponseEntity<TokenInfoVO> tempSocialLogin(
            @Valid @RequestBody AuthenticationDTO authenticationDTO) {
        if (!Objects.equals(authenticationDTO.getSysType(), SysTypeEnum.ORDINARY.value())) {
            return ServerResponseEntity.showFailMsg("sysType error");
        }
        if (StrUtil.isBlank(authenticationDTO.getTempUid())) {
            return ServerResponseEntity.showFailMsg("tempUid is blank");
        }
        return doLogin(authenticationDTO, LoginType.PASSWORD.value());
    }

    @PostMapping("/ua/ma/login")
    @Operation(summary = "微信小程序一键登录", description = "三合一登录（包含注册 + 绑定 + 登录）")
    public ServerResponseEntity<TokenInfoVO> maLogin(@Valid @RequestBody MaCodeAuthenticationDTO maCodeAuthenticationDTO) {
        WxMaPhoneNumberInfo newPhoneNoInfo;
        try {
            newPhoneNoInfo = wxConfig.getWxMaService().getUserService().getPhoneNoInfo(maCodeAuthenticationDTO.getCode());
        } catch (WxErrorException e) {
            throw new LuckException(e.getMessage());
        }
        // 没有区号的手机号，国外手机号会有区号
        String mobile = newPhoneNoInfo.getPurePhoneNumber();

        return threeInOneLogin(mobile, maCodeAuthenticationDTO.getTempUid(), SocialType.MA.value());
    }

    @PostMapping("/ua/adminLogin")
    @Operation(summary = "账号密码 + 验证码登录(用于后台登录)", description = "通过账号/手机号/用户名密码登录，还要携带用户的类型，也就是用户所在的系统")
    public ServerResponseEntity<TokenInfoVO> login(
            @Valid @RequestBody CaptchaAuthenticationDTO captchaAuthenticationDTO) {
        // 登陆后台登录需要再校验一遍验证码
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(captchaAuthenticationDTO.getCaptchaVerification());
        ResponseModel response = captchaService.verification(captchaVO);
        if (!response.isSuccess()) {
            return ServerResponseEntity.showFailMsg("验证码已失效，请重新获取");
        }
        return doLogin(captchaAuthenticationDTO, LoginType.PASSWORD.value());
    }


    private ServerResponseEntity<TokenInfoVO> doLogin(@RequestBody @Valid AuthenticationDTO authenticationDTO, Integer loginType) {
        ServerResponseEntity<UidInfoBO> uidInfoBOResponse = authAccountService.getUserInfoInTokenByInputUserNameAndPassword(
                authenticationDTO.getUserName(),
                authenticationDTO.getPassWord(),
                authenticationDTO.getSysType(),
                loginType);
        if (!uidInfoBOResponse.isSuccess()) {
            return ServerResponseEntity.transform(uidInfoBOResponse);
        }

        UidInfoBO data = uidInfoBOResponse.getData();

        // 将以前的权限清理了,以免权限有缓存
        menuPermissionService.clearUserPermissionsCache(data.getUserId(), data.getSysType());

        AuthSocial authSocial = null;
        if (StrUtil.isNotBlank(authenticationDTO.getTempUid())) {
            authSocial = authSocialService.getByTempUid(authenticationDTO.getTempUid());
            if (Objects.isNull(authSocial)) {
                return ServerResponseEntity.fail(ResponseEnum.TEMP_UID_ERROR);
            }
            // 社交账号未关联本系统账号，则进行绑定关联
            if (Objects.isNull(authSocial.getUid())) {
                authSocialService.bindSocial(authSocial, data);
            }
        }

        TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(data);

        if (Objects.nonNull(authSocial)) {
            // 缓存当前登录的openId
            LoginInfoBO loginInfoBO = new LoginInfoBO();
            loginInfoBO.setSocialType(authSocial.getSocialType());
            loginInfoBO.setBizUserId(authSocial.getBizUserId());
            RedisUtil.set(OauthCacheNames.LOGIN_INFO + tokenInfoVO.getAccessToken(),
                    JSONObject.toJSONString(loginInfoBO),
                    tokenInfoVO.getExpiresIn());
        }
        // 保存token，返回token数据给前端
        return ServerResponseEntity.success(tokenInfoVO);
    }


    @PostMapping(AuthConfigAdapter.LOGIN_OUT_URI)
    @Operation(summary = "退出登陆", description = "点击退出登陆，清除token，清除菜单缓存")
    public ServerResponseEntity<Void> loginOut(HttpServletRequest request) {
        String accessToken = request.getHeader(tokenName);
        UidInfoBO uidInfoBO = tokenStore.getUidInfoByCache(accessToken);
        LoginInfoBO loginInfoByCache = tokenStore.getLoginInfoByCache();
        if (uidInfoBO == null) {
            return ServerResponseEntity.success();
        }
        if (!Objects.equals(SysTypeEnum.ORDINARY.value(), uidInfoBO.getSysType())) {
            // 将以前的权限清理了,以免权限有缓存
            menuPermissionService.clearUserPermissionsCache(uidInfoBO.getUserId(), uidInfoBO.getSysType());
        }

        // 只有微信公众号和小程序的登录会进行登录并绑定的操作
        if (Objects.equals(loginInfoByCache.getSocialType(), SocialType.MA.value()) || Objects.equals(loginInfoByCache.getSocialType(), SocialType.MP.value())) {
            // 解绑
            authSocialMapper.unBindUser(loginInfoByCache.getBizUserId(), loginInfoByCache.getSocialType());
        }
        // 删除该用户在该系统当前的token
        StpUtil.logoutByTokenValue(accessToken);
        return ServerResponseEntity.success();
    }

    @PutMapping("/checkPhoneIsBind")
    @Operation(summary = "检查用户修改后的手机号是否已经绑定其他账号", description = "检查用户修改后的手机号是否已经绑定其他账号")
    public ServerResponseEntity<Boolean> checkPhoneIsBind(@RequestBody BindSocialDTO checkSmsDTO) {

        int accountNum = authAccountService.getByMobile(checkSmsDTO.getValidAccount());
        if (accountNum > 0) {
            // 此用户不存在，请先注册
            throw new LuckException("此手机号已经绑定过其他账号，请重新输入。");
        }
        return ServerResponseEntity.success(true);
    }


    /**
     * 三合一登录（包含注册 + 绑定 + 登录）
     */
    private ServerResponseEntity<TokenInfoVO> threeInOneLogin(String mobile, String tempUid, Integer socialType) {
        ServerResponseEntity<UidInfoBO> uidInfoBOResponse = authAccountService
                .getUserInfoInTokenByInputUserNameAndPassword(mobile,
                        null, SysTypeEnum.ORDINARY.value(), LoginType.ONE_CLICK_LOGIN.value());
        if (!uidInfoBOResponse.isSuccess()) {
            return ServerResponseEntity.transform(uidInfoBOResponse);
        }
        UidInfoBO uidInfoBO = uidInfoBOResponse.getData();
        AuthSocial authSocial = null;
        // 没有就注册
        if (uidInfoBO == null || uidInfoBO.getAccountUid() == null) {
            if (Objects.equals(socialType, SocialType.MA.value()) || Objects.equals(socialType, SocialType.MP.value())) {
                authSocial = authSocialService.getByTempUid(tempUid);
                if (authSocial == null || authSocial.getUid() != null) {
                    return ServerResponseEntity.fail(ResponseEnum.TEMP_UID_ERROR);
                }
            }
            UserRegisterDTO userRegisterDTO = new UserRegisterDTO();
            userRegisterDTO.setMobile(mobile);
            userRegisterDTO.setTempUid(tempUid);
            // 注册并绑定用户
            ServerResponseEntity<UidAndUserIdBO> saveResponse = userFeignClient.save(userRegisterDTO);
            if (!saveResponse.isSuccess()) {
                return ServerResponseEntity.transform(saveResponse);
            }
            UidAndUserIdBO uidAndUserIdBO = saveResponse.getData();
            uidInfoBO = new UidInfoBO();
            uidInfoBO.setUserId(uidAndUserIdBO.getUserId());
            uidInfoBO.setAccountUid(uidAndUserIdBO.getUid());
            uidInfoBO.setIsAdmin(0);
            uidInfoBO.setSysType(SysTypeEnum.ORDINARY.value());
        } else {
            authSocial = checkAndGetAppConnect(uidInfoBO.getAccountUid(), socialType, tempUid);
        }
        TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(uidInfoBO);
        if (authSocial != null) {
            // 缓存当前登录的openId
            LoginInfoBO loginInfoBO = new LoginInfoBO();
            loginInfoBO.setSocialType(authSocial.getSocialType());
            loginInfoBO.setBizUserId(authSocial.getBizUserId());
            RedisUtil.set(OauthCacheNames.LOGIN_INFO + tokenInfoVO.getAccessToken(),
                    JSONObject.toJSONString(loginInfoBO),
                    tokenInfoVO.getExpiresIn());
        }
        return ServerResponseEntity.success(tokenInfoVO);
    }

    /**
     * 登录并绑定用户
     */
    private AuthSocial checkAndGetAppConnect(Long uid, Integer socialType, String tempUid) {
        // 只有微信公众号和小程序的登录会进行登录并绑定的操作
        if (!Objects.equals(socialType, SocialType.MA.value()) && !Objects.equals(socialType, SocialType.MP.value())) {
            return null;
        }
        // 在SocialLoginController 当中，会返回一个tempUid用来换取openid的
        AuthSocial authSocial = authSocialService.getByTempUid(tempUid);
        if (authSocial == null) {
            throw new LuckException(ResponseEnum.TEMP_UID_ERROR);
        }
        // 二合一登录，啥意思呢？
        // 1. 绑定：将该账号和该微信的openId进行绑定
        // 2. 登录：返回token登录成功
        if (authSocial.getUid() == null) {
            authSocialMapper.bindUidByTempUid(uid, authSocial.getTempUid());
        }
        // 如果有userId就判断下是不是一样的user，不是的话，不能够绑定
        else if (!Objects.equals(authSocial.getUid(), uid)) {
            throw new LuckException(ResponseEnum.SOCIAL_ACCOUNT_BIND_BY_OTHER);
        }
        return authSocial;
    }

}
