package com.flossom.miniProgram.service.impl;

import com.alibaba.fastjson.JSON;
import com.flossom.common.core.constant.CacheConstants;
import com.flossom.common.core.constant.HttpStatus;
import com.flossom.common.core.constant.UserConstants;
import com.flossom.common.core.domain.R;
import com.flossom.common.core.domain.entity.*;
import com.flossom.common.core.enums.IntegralChangeTypeEnum;
import com.flossom.common.core.enums.MessageTypeEnum;
import com.flossom.common.core.enums.WxUserIntegralMessageTypeEnum;
import com.flossom.common.core.exception.ServiceException;
import com.flossom.common.core.mapper.*;
import com.flossom.common.core.utils.DateUtils;
import com.flossom.common.core.utils.StringUtils;
import com.flossom.common.redis.service.RedisService;
import com.flossom.common.security.utils.SecurityUtils;
import com.flossom.miniProgram.domain.vo.*;
import com.flossom.miniProgram.service.IWxUserMemberService;
import com.flossom.miniProgram.utils.MiniProgramUtils;
import com.flossom.system.api.RemoteAuthService;
import com.flossom.system.api.model.LoginUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class WxUserMemberServiceImpl implements IWxUserMemberService {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private WxUserMemberMapper wxUserMemberMapper;

    @Autowired
    private RemoteAuthService remoteAuthService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private WxScriptMessageMapper wxScriptMessageMapper;

    @Autowired
    private WxUserScriptLogMapper wxUserScriptLogMapper;

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private IntegralGlobalMapper integralGlobalMapper;

    @Autowired
    private WxUserIntegralLogMapper wxUserIntegralLogMapper;


    @Override
    @Transactional
    public WxUserMember wxRegister(String code) throws Exception {
        // 1、使用临时凭证 code 获取 appi + appsecret + unionid
        String result = MiniProgramUtils.getSessionKeyAndOpenId(code);
        logger.info("请求微信服务器获取openid和unionid，返回结果：{}", result);
        WxCode2SessionRet wxCode2SessionRet = JSON.parseObject(result, WxCode2SessionRet.class);
        if (wxCode2SessionRet.getErrcode() != null) {
            logger.error(wxCode2SessionRet.getErrmsg());
            throw new ServiceException("登录失败");
        }
        logger.info("openid = {}", wxCode2SessionRet.getOpenid());
        logger.info("unionid = {}", wxCode2SessionRet.getUnionid());

        // 2、根据 openid 查询用户信息
        WxUserMember wxUserMember = wxUserMemberMapper.selectWxUserMemberByOpenId(wxCode2SessionRet.getOpenid());
        if (wxUserMember == null) {
            // 未注册用户，就注册游客账户
            // 2.1、封装微信用户信息
            wxUserMember = new WxUserMember();
            // 即将过期积分，默认为0
            wxUserMember.setExpireCredit(0);
            wxUserMember.setOpenid(wxCode2SessionRet.getOpenid());
            wxUserMember.setUnionid(wxCode2SessionRet.getUnionid());
            wxUserMember.setLoginTime(DateUtils.getNowDate());
            // 2.2、微信用户表注册
            wxUserMemberMapper.insertWxUserMember(wxUserMember);
            wxUserMember = wxUserMemberMapper.selectWxUserMemberById(wxUserMember.getId());
            // 2.3、系统表注册
            SysUser user = new SysUser();
            user.setUserName(wxUserMember.getOpenid());
            user.setNickName(UserConstants.WX_NICKNAME);
            user.setPassword(SecurityUtils.encryptPassword(UserConstants.WX_SYSTEM_USER_PASSWORD));
            // 用户类型01:微信会员用户
            user.setUserType("01");
            user.setCreateBy(SecurityUtils.getUsername());
            sysUserMapper.insertUser(user);
        }

        /**
         * 临时将 sessionKey 放到 wxUserMember，调用登录时方便获取
         */
        wxUserMember.getParams().put(CacheConstants.WX_SESSION_KEY_CACHE + wxUserMember.getOpenid(),
                wxCode2SessionRet.getSession_key());
        return wxUserMember;
    }

    @Override
    public LoginUserVo login(WxUserMember wxUserMember) {
        // 3、登录请求，获取 token
        R<Map<String, String>> loginResult = remoteAuthService.wxLogin(wxUserMember);
        if (loginResult.getCode() == R.FAIL) {
            logger.error("获取token失败：{}", loginResult.getMsg());
            throw new ServiceException("登录失败");
        }
        /**
         * TODO: 将 session_key 缓存在 redis，并且由于官方没有规定 session_key 的有效期，所以使用时候要注意如下：
         *        当需要使用的时候，需要前端先判断微信的登录态是否过期（wx.checkSession()）
         *          1、如果过期，重新调用登录接口获取新的 session_key
         *          2、如果没过期，但由于security的拦截器会判断过期时间不足120分钟时会，会对session_key进行续期：{@link com.flossom.common.security.service.TokenService#refreshSessionKey}
         */
        String sessionKey = (String) wxUserMember.getParams().get(CacheConstants.WX_SESSION_KEY_CACHE + wxUserMember.getOpenid());
        if (StringUtils.isBlank(sessionKey)) {
            logger.error("sessionKey 为空");
            throw new ServiceException("登录失败");
        }
        redisService.setCacheObject(CacheConstants.WX_SESSION_KEY_CACHE + wxUserMember.getOpenid(),
                sessionKey, CacheConstants.EXPIRATION, TimeUnit.MINUTES);

        // 4、返回登录信息
        LoginUserVo loginUserVo = new LoginUserVo();
        BeanUtils.copyProperties(wxUserMember, loginUserVo);
        loginUserVo.setToken(loginResult.getData().get("access_token"));
        logger.info("登录用户信息：id = {}, username = {}, token={}", loginUserVo.getId(), loginUserVo.getUsername(), loginUserVo.getToken());
        return loginUserVo;
    }

    @Override
    public String upgradeMember(String code) throws Exception {
        // 获取手机号
        String result = MiniProgramUtils.getPhone(code);
        logger.info("请求微信服务器获取手机号码返回结果：{}", result);
        WxCode2PhoneRet wxCode2PhoneRet = JSON.parseObject(result, WxCode2PhoneRet.class);
        if (wxCode2PhoneRet.getErrcode() != 0) {
            logger.error("获取用户手机号码失败: {}", wxCode2PhoneRet.getErrmsg());
            throw new ServiceException("获取用户手机号码失败");
        }

        // 查询号码是否存在
        List<WxUserMember> wxUserMembers = wxUserMemberMapper.selectWxUserMemberByMobile(wxCode2PhoneRet.getPhoneInfo().getPhoneNumber());
        if (wxUserMembers.size() > 0) {
            throw new ServiceException("手机号码已注册！");
        }
        wxUserMemberMapper.upgradeMember(wxCode2PhoneRet.getPhoneInfo().getPhoneNumber(), SecurityUtils.getLoginUser().getWxUserMember().getId().intValue());

        // 2.4、用户注册成功发送消息
        WxScriptMessage wxScriptMessage = wxScriptMessageMapper.selectOneByMessageType(MessageTypeEnum.USER_REGISTRATION.getCode());
        if (wxScriptMessage != null) {
            WxUserScriptLog wxUserScriptLog = new WxUserScriptLog();
            BeanUtils.copyProperties(wxScriptMessage, wxUserScriptLog);
            wxUserScriptLog.setId(null);
            wxUserScriptLog.setWxUserId(SecurityUtils.getLoginUser().getWxUserMember().getId());
            wxUserScriptLog.setMessageType(WxUserIntegralMessageTypeEnum.USER_REGISTRATION.getCode());
            wxUserScriptLog.setIsCustom(0);
            wxUserScriptLog.setTitile(wxScriptMessage.getMessageTitle());
            wxUserScriptLog.setContent(wxScriptMessage.getMessageContent());
            wxUserScriptLog.setCreateTime(DateUtils.getNowDate());
            wxUserScriptLog.setCreateBy(null);
            wxUserScriptLog.setUpdateTime(null);
            wxUserScriptLog.setUpdateBy(null);
            wxUserScriptLogMapper.insertWxUserScriptLog(wxUserScriptLog);
        }

        /**
         * TODO: 新用户注册，需要对接数云，将用户信息传给数云
         */
        return wxCode2PhoneRet.getPhoneInfo().getPhoneNumber();
    }

    @Override
    @Transactional
    public LoginUserVo updateUser(UserMemberUpdateVo userMemberUpdateVo) {
        // 完善用户信息, 判断是否标记完善信息
        WxUserMember wxUserMember = wxUserMemberMapper.selectWxUserMemberByOpenId(SecurityUtils.getLoginUser().getWxUserMember().getOpenid());
        if (wxUserMember == null) {
            throw new ServiceException("用户不存在");
        }
        int floatScore = 0;
        Boolean isCompleteInformation = false;
        if (wxUserMember.getIsCompleteInformation() == null || wxUserMember.getIsCompleteInformation() == 0) {
            isCompleteInformation = true;
            // 修改完善状态
            wxUserMember.setIsCompleteInformation(1);
            List<IntegralGlobal> integralGlobalList = integralGlobalMapper.selectIntegralGlobalList(new IntegralGlobal());
            IntegralGlobal integralGlobal = integralGlobalList.get(0);
            floatScore = integralGlobal.getIntegral().intValue();
            wxUserMember.setCredit(wxUserMember.getCredit() + integralGlobal.getIntegral().intValue());

            // 保存积分详情
            WxUserIntegralLog wxUserIntegralLog = new WxUserIntegralLog();
            wxUserIntegralLog.setUserId(SecurityUtils.getLoginUser().getWxUserMember().getId());
            wxUserIntegralLog.setUserName(userMemberUpdateVo.getNickname());
            wxUserIntegralLog.setUserPhone(wxUserMember.getMobile());
            wxUserIntegralLog.setSource(IntegralChangeTypeEnum.INCREASE.getCode());
            wxUserIntegralLog.setFloatScore(integralGlobal.getIntegral());
            wxUserIntegralLog.setRemarkContent("首次完善用户信息");
            wxUserIntegralLog.setCreateBy(SecurityUtils.getLoginUser().getWxUserMember().getNickname());
            wxUserIntegralLog.setCreateTime(DateUtils.getNowDate());
            wxUserIntegralLogMapper.insertWxUserIntegralLog(wxUserIntegralLog);

            // 2.4、用户注册成功发送消息
            WxScriptMessage wxScriptMessage = wxScriptMessageMapper.selectOneByMessageType(MessageTypeEnum.COMPLETE_USER_INFORMATION.getCode());
            if (wxScriptMessage != null) {
                WxUserScriptLog wxUserScriptLog = new WxUserScriptLog();
                BeanUtils.copyProperties(wxScriptMessage, wxUserScriptLog);
                wxUserScriptLog.setId(null);
                wxUserScriptLog.setWxUserId(SecurityUtils.getLoginUser().getWxUserMember().getId());
                wxUserScriptLog.setMessageType(WxUserIntegralMessageTypeEnum.COMPLETE_USER_INFORMATION.getCode());
                wxUserScriptLog.setIsCustom(0);
                wxUserScriptLog.setTitile(wxScriptMessage.getMessageTitle());
                wxUserScriptLog.setContent(wxScriptMessage.getMessageContent());
                wxUserScriptLog.setCreateTime(DateUtils.getNowDate());
                wxUserScriptLog.setCreateBy(null);
                wxUserScriptLog.setUpdateTime(null);
                wxUserScriptLog.setUpdateBy(null);
                wxUserScriptLogMapper.insertWxUserScriptLog(wxUserScriptLog);
            }

            // TODO： 首次完善，增加加分 (对接数云未完成)
        }
        BeanUtils.copyProperties(userMemberUpdateVo, wxUserMember);
        wxUserMember.setUpdateTime(DateUtils.getNowDate());
        wxUserMemberMapper.updateWxUserMember(wxUserMember);

        // 刷新用户信息
        LoginUserVo loginUserVo = refreshWxUserInfo();
        if (isCompleteInformation) {
            loginUserVo.setIntegralText("您已完善个人信息，获得" + floatScore + "积分");
        }
        return loginUserVo;
    }

    /**
     * 重构用户缓存信息
     *
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public LoginUserVo refreshWxUserInfo() {
        LoginUser loginUser = SecurityUtils.getLoginUser();

        // 响应最新数据
        String openid = loginUser.getWxUserMember().getOpenid();
        WxUserMember wxUserMember = wxUserMemberMapper.selectWxUserMemberByOpenId(openid);
        if (wxUserMember == null) {
            logger.error("获取用户信息失败，token用户ID：{}， token用户openid：{}，token用户昵称：{}",
                    loginUser.getWxUserMember().getId(), loginUser.getWxUserMember().getNickname());
            throw new ServiceException("获取用户信息失败", HttpStatus.UNAUTHORIZED);
        }
        LoginUserVo loginUserVo = new LoginUserVo();
        BeanUtils.copyProperties(wxUserMember, loginUserVo);
        loginUserVo.setToken(null);
        loginUserVo.setIntegralText(null);

        // 刷新缓存信息
        String userKey = CacheConstants.LOGIN_TOKEN_KEY + loginUser.getToken();
        loginUser.setWxUserMember(wxUserMember);
        redisService.setCacheObject(userKey, loginUser, CacheConstants.EXPIRATION, TimeUnit.MINUTES);
        return loginUserVo;
    }

}
