package cn.newfeifan.mall.module.member.service.user;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.*;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import cn.newfeifan.mall.framework.common.enums.CommonStatusEnum;
import cn.newfeifan.mall.framework.common.enums.UserTypeEnum;
import cn.newfeifan.mall.framework.common.exception.ErrorCode;
import cn.newfeifan.mall.framework.common.util.object.BeanUtils;
import cn.newfeifan.mall.module.member.controller.app.user.vo.*;
import cn.newfeifan.mall.module.member.convert.auth.AuthConvert;
import cn.newfeifan.mall.module.member.dal.dataobject.user.MemberUserDO;
import cn.newfeifan.mall.module.member.dal.mysql.user.MemberUserMapper;
import cn.newfeifan.mall.module.member.mq.producer.shareLinkRegister.ShareLinkRegisterProducer;
import cn.newfeifan.mall.module.member.mq.producer.sharePath.SharePathProducer;
import cn.newfeifan.mall.module.member.mq.producer.user.MemberUserProducer;
import cn.newfeifan.mall.module.system.api.sms.SmsCodeApi;
import cn.newfeifan.mall.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.newfeifan.mall.module.system.api.social.SocialClientApi;
import cn.newfeifan.mall.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
import cn.newfeifan.mall.module.system.enums.sms.SmsSceneEnum;
import cn.newfeifan.mall.module.system.service.user.AdminUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.regex.Matcher;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

import static cn.newfeifan.mall.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.newfeifan.mall.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.newfeifan.mall.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.newfeifan.mall.module.member.enums.DictTypeConstants.*;
import static cn.newfeifan.mall.module.member.enums.ErrorCodeConstants.*;
import static cn.newfeifan.mall.module.system.constant.SystemConstants.WX_TICK_KEY_APPID;

/**
 * 会员 User Service 实现类
 *
 * @author 非繁源码
 */
@Service
@Valid
@Slf4j
public class MemberUserServiceImpl implements MemberUserService {

    @Resource
    private MemberUserMapper memberUserMapper;

    @Resource
    private SmsCodeApi smsCodeApi;

    @Resource
    private SocialClientApi socialClientApi;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private MemberUserProducer memberUserProducer;

    @Resource
    private SharePathProducer sharePathProducer;
    @Resource
    private ShareLinkRegisterProducer shareLinkRegisterProducer;
    @Resource
    private AdminUserService adminUserService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Value("${wx.mp.app-id}")
    private String appid;
    @Value("${wx.mp.secret}")
    private String Wxgsecret;

    @Override
    public MemberUserDO getUserByMobile(String mobile) {
        return memberUserMapper.selectByMobile(mobile);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MemberUserDO createUser(String mobile, String nickname, String avtar, String registerIp, Integer terminal, Long linkId, String username, String password) {
        return privateCreateUser(mobile, nickname, avtar, registerIp, terminal, linkId, username, password);
    }

    @Transactional(rollbackFor = Exception.class)
    public MemberUserDO privateCreateUser(String mobile, String nickname, String avtar,
                                          String registerIp, Integer terminal, Long linkId, String username, String password) {
        // 通过手机号查询系统用户编号
        Long systemUserId = adminUserService.getUserIdByMobile(mobile);

        // 生成密码
        if (password == null) {
            password = IdUtil.fastSimpleUUID();
        }
        // 插入用户
        MemberUserDO user = new MemberUserDO();
        user.setUsername(username);
        user.setMobile(mobile);
        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
        user.setPassword(encodePassword(password)); // 加密密码
        user.setRegisterIp(registerIp).setRegisterTerminal(terminal);
        user.setSystemUsersId(systemUserId);    //绑定系统用户
        user.setAvatar(avtar); // 基础信息
        user.setVisitor(linkId == null);
        memberUserMapper.insert(user);

        // 如果注册的时候关系链创建失败则需要返回让用户重新注册
        try {
            if (linkId != null) {
                shareLinkRegisterProducer.sendShareLinkRegisterMessage(linkId, user.getId());
            } else {
                linkId = 1L;
            }
            sharePathProducer.sendSharePathCreatMessage(linkId, user.getId(), username, mobile);
            memberUserProducer.sendUserCreateMessage(user.getId());
        } catch (Exception e) {
            throw exception(RELOAD_REGISTER);
        }

        return user;
    }

    //现在这个不用了
//    @Transactional(rollbackFor = Exception.class)
//    public void registerTransactionSynchronization(Long userId, Long linkId) {
//        // 发送 消息：用户创建
//        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
//            @Override
//            public void afterCommit() {
//                sharePathProducer.sendSharePathCreatMessage(linkId, userId);
//                if (linkId != null) {
//                    shareLinkRegisterProducer.sendShareLinkRegisterMessage(linkId, userId);
//                }
//                memberUserProducer.sendUserCreateMessage(userId);
//            }
//        });
//    }

    @Override
    public void updateUserLogin(Long id, String loginIp) {
        memberUserMapper.updateById(new MemberUserDO().setId(id)
                .setLoginIp(loginIp).setLoginDate(LocalDateTime.now()));
    }

    @Override
    public MemberUserDO getUser(Long id) {
        return memberUserMapper.selectById(id);
    }

    @Override
    public List<MemberUserDO> getUserList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return ListUtil.empty();
        }
        return memberUserMapper.selectBatchIds(ids);
    }

    @Override
    public void updateUser(Long userId, AppMemberUserUpdateReqVO reqVO) {

        MemberUserDO user = memberUserMapper.selectById(userId);

        // 如果用户名被修改，则校验是否被有同名
        if (reqVO.getUsername() != null && !reqVO.getUsername().equals(user.getUsername())) {
            Long count = selectCountByUsername(reqVO.getUsername());
            if (count > 0) {
                throw exception(AUTH_USERNAME_EXISTS);
            }
        }

        MemberUserDO updateObj = BeanUtils.toBean(reqVO, MemberUserDO.class).setId(userId);
        if (memberUserMapper.updateById(updateObj) > 0 && StrUtil.isNotEmpty(reqVO.getNickname())) {
            //同步修改其它地方保留的用户名
            memberUserMapper.synchronizationUserNicknameForDUser(reqVO.getNickname(), userId);
            memberUserMapper.synchronizationUserNicknameForIntegral(reqVO.getNickname(), userId);
            memberUserMapper.synchronizationUserNicknameForSharePathDesc(reqVO.getNickname(), userId);
            memberUserMapper.synchronizationUserNicknameForSharePathAnc(reqVO.getNickname(), userId);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserMobile(Long userId, AppMemberUserUpdateMobileReqVO reqVO) {
        // 1.1 检测用户是否存在
        MemberUserDO user = validateUserExists(userId);
        // 1.2 校验新手机是否已经达到绑定上限
        if (getUserCountByMobile(reqVO.getMobile()) >= MOBILE_REGISTER_MAX) {
            throw exception(MOBILE_IS_MAXIMIZE);
        }
//        validateMobileUnique(null, reqVO.getMobile());

        // 2.1 校验旧手机和旧验证码
        // 补充说明：从安全性来说，老手机也校验 oldCode 验证码会更安全。但是由于 uni-app 商城界面暂时没做，所以这里不强制校验
        if (StrUtil.isNotEmpty(reqVO.getOldCode())) {
            smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(user.getMobile()).setCode(reqVO.getOldCode())
                    .setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));
        }
        // 2.2 使用新验证码
        smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getMobile()).setCode(reqVO.getCode())
                .setScene(SmsSceneEnum.MEMBER_UPDATE_NEW_MOBILE.getScene()).setUsedIp(getClientIP()));

        // 3. 更新用户手机
        memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build());
    }

    @Override
    public void updateUserMobileByWeixin(Long userId, AppMemberUserUpdateMobileByWeixinReqVO reqVO) {
        // 1.1 获得对应的手机号信息
        SocialWxPhoneNumberInfoRespDTO phoneNumberInfo = socialClientApi.getWxMaPhoneNumberInfo(
                UserTypeEnum.MEMBER.getValue(), reqVO.getCode());
        Assert.notNull(phoneNumberInfo, "获得手机信息失败，结果为空");
        // 1.2 校验新手机是否已经被绑定
        validateMobileUnique(userId, phoneNumberInfo.getPhoneNumber());

        // 2. 更新用户手机
        memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(phoneNumberInfo.getPhoneNumber()).build());
    }

    @Override
    public void updateUserPassword(Long userId, AppMemberUserUpdatePasswordReqVO reqVO) {
        // 检测用户是否存在
        MemberUserDO user = validateUserExists(userId);
        // 校验验证码
        smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(user.getMobile()).setCode(reqVO.getCode())
                .setScene(SmsSceneEnum.MEMBER_UPDATE_PASSWORD.getScene()).setUsedIp(getClientIP()));

        // 更新用户密码
        memberUserMapper.updateById(MemberUserDO.builder().id(userId)
                .password(passwordEncoder.encode(reqVO.getPassword())).build());
    }

    @Override
    public void resetUserPassword(AppMemberUserResetPasswordReqVO reqVO) {
        // 检验用户是否存在
        MemberUserDO user = validateUserExists(reqVO.getMobile());

        // 使用验证码
        smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_RESET_PASSWORD,
                getClientIP()));

        // 更新密码
        memberUserMapper.updateById(MemberUserDO.builder().id(user.getId())
                .password(passwordEncoder.encode(reqVO.getPassword())).build());
    }

    private MemberUserDO validateUserExists(String mobile) {
        MemberUserDO user = memberUserMapper.selectByMobile(mobile);
        if (user == null) {
            throw exception(USER_MOBILE_NOT_EXISTS);
        }
        return user;
    }

    @Override
    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }


    /**
     * 对密码进行加密
     *
     * @param password 密码
     * @return 加密后的密码
     */
    private String encodePassword(String password) {
        return passwordEncoder.encode(password);
    }


    @VisibleForTesting
    MemberUserDO validateUserExists(Long id) {
        if (id == null) {
            return null;
        }
        MemberUserDO user = memberUserMapper.selectById(id);
        if (user == null) {
            throw exception(USER_NOT_EXISTS);
        }
        return user;
    }

    @VisibleForTesting
    void validateMobileUnique(Long id, String mobile) {
        if (StrUtil.isBlank(mobile)) {
            return;
        }
        MemberUserDO user = memberUserMapper.selectByMobile(mobile);
        if (user == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的用户
        if (id == null) {
            throw exception(USER_MOBILE_USED, mobile);
        }
        if (!user.getId().equals(id)) {
            throw exception(USER_MOBILE_USED, mobile);
        }
    }

    @Override
    public void updateUserLevel(Long id, Long levelId, Integer experience) {
        // 0 代表无等级：防止UpdateById时，会被过滤掉的问题
        levelId = ObjectUtil.defaultIfNull(levelId, 0L);
        memberUserMapper.updateById(new MemberUserDO()
                .setId(id)
                .setLevelId(levelId).setExperience(experience)
        );
    }

    @Override
    public Long getUserCountByGroupId(Long groupId) {
        return memberUserMapper.selectCountByGroupId(groupId);
    }

    @Override
    public Long getUserCountByLevelId(Long levelId) {
        return memberUserMapper.selectCountByLevelId(levelId);
    }

    @Override
    public Long getUserCountByTagId(Long tagId) {
        return memberUserMapper.selectCountByTagId(tagId);
    }

    @Override
    public Long selectCountByUsername(String username) {
        return memberUserMapper.selectCount(MemberUserDO::getUsername, username);
    }

    @Override
    public Long getUserCountByMobile(String mobile) {
        return memberUserMapper.selectCount(MemberUserDO::getMobile, mobile);
    }

    @Override
    public List<MemberUserDO> getUserListBySystemUserId(Long systemUserId) {
        return memberUserMapper.selectList(MemberUserDO::getSystemUsersId, systemUserId);
    }

    @Override
    public MemberUserDO getUserByMobile(String mobile, Long userId) {
        return memberUserMapper.selectOne(MemberUserDO::getMobile, mobile, MemberUserDO::getId, userId);
    }

    @Override
    public void updateUserByAlipayAccount(Long loginUserId, AppMemberUserUpdateAlipayAccountReqVO reqVO) {
        if (isValidPhoneNumber(reqVO.getAlipayAccount()) || validateEmail(reqVO.getAlipayAccount())) {
            MemberUserDO user = BeanUtils.toBean(reqVO, MemberUserDO.class);
            user.setId(loginUserId);
            memberUserMapper.updateById(user);
        } else {
            ErrorCode ALIPAY_ACCOUNT_ERROR = new ErrorCode(1_004_001_000, "支付宝账号格式错误");
            throw exception(ALIPAY_ACCOUNT_ERROR);
        }
    }

    @Override
    public void updateUserByBankAccount(Long loginUserId, AppMemberUserUpdateBankAccountReqVO reqVO) {
        if (!isAccountFormatValid(reqVO.getBankAccount())) {
            ErrorCode BANK_COUNT_ERROR = new ErrorCode(1_004_001_000, "银行账号格式错误");
            throw exception(BANK_COUNT_ERROR);
        }

        MemberUserDO user = BeanUtils.toBean(reqVO, MemberUserDO.class);
        user.setId(loginUserId);
        memberUserMapper.updateById(user);
    }

    @Override
    public List<MemberUserDO> selectListByMobile(String mobile) {
        return memberUserMapper.selectList(MemberUserDO::getMobile, mobile);
    }

    @Override
    public MemberUserDO getUserByUserNameWithMobile(String username, String mobile) {
        return memberUserMapper.selectOne(MemberUserDO::getUsername, username, MemberUserDO::getMobile, mobile);
    }

    @Override
    public void addRegisterId(String registerId) {
        MemberUserDO memberUserDO = memberUserMapper.selectById(getLoginUserId());
        if (registerId != null && !registerId.equals(memberUserDO.getAndroidRegisterId())) {
            // 防止一台设备登录多个账号会导致异常发送的可能,所以如果有同样的registerId就先删除后加入
            List<MemberUserDO> users = memberUserMapper.selectList(MemberUserDO::getAndroidRegisterId, registerId);
            for (MemberUserDO user : users) {
                user.setAndroidRegisterId("");
                memberUserMapper.update(new LambdaUpdateWrapper<MemberUserDO>().setSql(" android_register_id = null ").eq(MemberUserDO::getId, user.getId()));
            }

            memberUserDO.setAndroidRegisterId(registerId);
            memberUserMapper.updateById(memberUserDO);
        }
    }

    @Override
    public List<String> getUserRsgIds(Long systemUserId, Long userId) {
        List<MemberUserDO> memberUserDOS;
        if (systemUserId != null) {
            memberUserDOS = memberUserMapper.selectList(new LambdaQueryWrapper<MemberUserDO>()
                    .eq(MemberUserDO::getSystemUsersId, systemUserId)
                    .isNotNull(MemberUserDO::getAndroidRegisterId));
        } else {
            memberUserDOS = memberUserMapper.selectList(new LambdaQueryWrapper<MemberUserDO>()
                    .eq(MemberUserDO::getId, userId)
                    .isNotNull(MemberUserDO::getAndroidRegisterId));

        }
        return memberUserDOS.stream().map(MemberUserDO::getAndroidRegisterId).collect(Collectors.toList());
    }

    @Override
    public void deleteUserById(Long loginUserId) {
        memberUserMapper.deleteById(loginUserId);
    }

    @Override
    public void updateUserPayPassword(Long userId, AppMemberUserUpdatePayPasswordReqVO reqVO) {
        // 检测用户是否存在
        MemberUserDO user = validateUserExists(userId);

        if (user.getPayPassword() != null) {
            // 判断原密码是否匹配
            if (!passwordEncoder.matches(reqVO.getOldPayPassword(), user.getPayPassword())) {
                throw exception(ORDER_USER_PAY_PASS_WORD_ERROR);
            }
        }

        // 更新用户密码
        memberUserMapper.updateById(MemberUserDO.builder().id(userId)
                .payPassword(passwordEncoder.encode(reqVO.getPayPassword())).build());
    }

    private Boolean isSubscribe(String openid, int maxRetries) {
        String requestUrl = StrUtil.format("https://api.weixin.qq.com/cgi-bin/user/info?access_token={}&openid={}&lang=zh_CN", getAccessToken(), openid);
        String returnMsg = HttpUtil.get(requestUrl);
        cn.hutool.json.JSONObject responseJsonObject = JSONUtil.parseObj(returnMsg);
        if (ObjectUtil.isNull(responseJsonObject)) try {
            throw new Exception("响应异常:获取信息为空!");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        log.info("用户信息:{}", responseJsonObject);
        Integer subscribe = (Integer) responseJsonObject.get("subscribe");
        if (subscribe == null) {
            Object errCode = responseJsonObject.get("errcode");
//            如果微信的accessToken失效,则重新获取,最多重试3次
            if (errCode.toString().equals("40001") && maxRetries <= 3) {
                stringRedisTemplate.delete(WX_TICK_KEY_APPID);
                isSubscribe(openid, maxRetries + 1);
            }
            return false;
        }
        // 微信返回的数据中1为关注，0为未关注
        return subscribe == 1;
    }

    /**
     * 获取accessToken
     *
     * @return accessToken
     */
    private String getAccessToken() {

        String accessToken = stringRedisTemplate.opsForValue().get(WX_TICK_KEY_APPID);

        if (StrUtil.isEmpty(accessToken)) {
            // 服务号的appid以及秘钥
            String requestUrl = StrUtil.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}", appid, Wxgsecret);
            String returnMsg = HttpUtil.get(requestUrl);
            cn.hutool.json.JSONObject responseJsonObject = JSONUtil.parseObj(returnMsg);
            if (ObjectUtil.isNull(responseJsonObject)) try {
                throw new Exception("响应异常:获取信息为空!");
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            accessToken = responseJsonObject.getStr("access_token");

            //微信的accessToken的失效时间是两个小时,这里改为一小时五十五分钟,怕双方的时间误差导致accessToken失效
            long expireTime = 60 * 60 + 55 * 60; // 1小时55分钟转换为秒
            stringRedisTemplate.opsForValue().set(WX_TICK_KEY_APPID, accessToken, expireTime, java.util.concurrent.TimeUnit.SECONDS);
        }

        log.info("===========Access_token:{}", accessToken);

        return accessToken;
    }

    /**
     * 校验手机号码
     *
     * @param phoneNumber 手机号
     * @return 是否合法
     */
    private boolean isValidPhoneNumber(String phoneNumber) {
        return PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches();
    }

    /**
     * 校验邮箱
     *
     * @param email 邮箱
     * @return 是否合法
     */
    private boolean validateEmail(String email) {
        Matcher matcher = PATTERN.matcher(email);
        return matcher.find();
    }

    /**
     * 校验银行账号
     *
     * @param accountNumber 银行账号
     * @return 是否合法
     */
    private boolean isAccountFormatValid(String accountNumber) {
        return accountNumber.matches("^\\d+$");
    }

}
