package com.wecloud.im.externalaccess.service;

import io.geekidea.springbootplus.framework.common.exception.BusinessException;
import io.geekidea.springbootplus.framework.common.service.impl.BaseServiceImpl;

import java.util.Date;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wecloud.im.constant.NumberConstant;
import com.wecloud.im.constant.RedisKeyPrefixConstant;
import com.wecloud.im.entity.ImApplication;
import com.wecloud.im.entity.ImClient;
import com.wecloud.im.entity.ImClientDevice;
import com.wecloud.im.enums.FriendStateEnum;
import com.wecloud.im.enums.RelationEnum;
import com.wecloud.im.enums.VerifySceneEnum;
import com.wecloud.im.externalaccess.entity.User;
import com.wecloud.im.externalaccess.mapper.UserMapper;
import com.wecloud.im.externalaccess.param.ChangePhoneParam;
import com.wecloud.im.externalaccess.param.CheckPhoneParam;
import com.wecloud.im.externalaccess.param.FindUserParam;
import com.wecloud.im.externalaccess.param.GetUserParam;
import com.wecloud.im.externalaccess.param.LoginSuccessDto;
import com.wecloud.im.externalaccess.param.ModifyHeadPortraitParam;
import com.wecloud.im.externalaccess.param.ModifyLandouParam;
import com.wecloud.im.externalaccess.param.ModifyNicknameParam;
import com.wecloud.im.externalaccess.param.ModifySexParam;
import com.wecloud.im.externalaccess.param.ResetPasswordParam;
import com.wecloud.im.externalaccess.param.UserBaseDto;
import com.wecloud.im.externalaccess.param.UserLoginParam;
import com.wecloud.im.externalaccess.param.UserRegisterParam;
import com.wecloud.im.friend.entity.ImFriend;
import com.wecloud.im.friend.service.ImFriendService;
import com.wecloud.im.param.add.ImClientHeadPortraitAdd;
import com.wecloud.im.param.add.ImClientNicknameUpdate;
import com.wecloud.im.service.ImApplicationService;
import com.wecloud.im.service.ImClientBlacklistService;
import com.wecloud.im.service.ImClientDeviceService;
import com.wecloud.im.service.ImClientService;
import com.wecloud.im.ws.utils.RedisUtils;
import com.wecloud.utils.AesUtil;
import com.wecloud.utils.RandomUtil;
import com.wecloud.utils.SnowflakeUtil;

/**
 * @Author wenzhida
 * @Date 2022/2/21 17:05
 * @Description 用户服务接口
 */
@Service
public class UserService extends BaseServiceImpl<UserMapper, User> {


    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private ImApplicationService imApplicationService;

    @Autowired
    private ImClientService imClientService;

    @Autowired
    private ImClientDeviceService imClientDeviceService;

    @Autowired
    private ImFriendService imFriendService;

    @Autowired
    private ImClientBlacklistService imClientBlacklistService;


    /**
     * 校验手机号码是否可使用
     *
     * @param param
     */
    public Boolean checkPhone(CheckPhoneParam param) {
        User userExist = this.getOne(new QueryWrapper<User>().lambda()
                .eq(User::getPhone, param.getPhone()));
        if (userExist != null) {
            throw new BusinessException("该手机号已被注册");
        }
        return Boolean.TRUE;
    }

    /**
     * 注册用户
     *
     * @param param
     */
    public String registerUser(UserRegisterParam param) {
        String key = new StringBuilder(RedisKeyPrefixConstant.VERIFY_CODE_PREFIX).append(VerifySceneEnum.REGISTER.getCode()).append(param.getPhone()).toString();
        this.verifySMSVerifyCode(param.getVerifyCode(), key);
        User userExist = this.getOne(new QueryWrapper<User>().lambda()
                .eq(User::getPhone, param.getPhone()));
        if (userExist != null) {
            throw new BusinessException("已存在此电话号码用户");
        }
        User user = new User();
        user.setId(SnowflakeUtil.getId());
        user.setNickname("蓝豆-" + RandomUtil.generateRandomStr(6));
        user.setIdNumber(this.getLandouNo());
        user.setPhone(param.getPhone());
        user.setPassword(AesUtil.encrypt(param.getPassword()));
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        this.save(user);
        return String.valueOf(user.getId());
    }

    /**
     * 用户登录
     *
     * @param param
     */
    public LoginSuccessDto loginUser(UserLoginParam param) {
        //  根据appKey从数据库查询密钥
        ImApplication imApplication = imApplicationService.getCacheAppByAppKey(param.getAppKey());
        if (imApplication == null) {
            throw new BusinessException("查无应用信息");
        }
        User user = this.getOne(new QueryWrapper<User>().lambda()
                .eq(User::getPhone, param.getPhone()));
        if (user == null) {
            throw new BusinessException("该手机号码还未注册");
        }
        if (!param.getPassword().equals(AesUtil.decrypt(user.getPassword()))) {
            throw new BusinessException("账户或密码错误");
        }

        // 判断client是否存在
        ImClient imClient = imClientService.getOne(new QueryWrapper<ImClient>().lambda()
                .eq(ImClient::getFkAppid, imApplication.getId())
                .eq(ImClient::getClientId, user.getId()));
        if (imClient == null) {
            imClient = new ImClient();
            imClient.setId(SnowflakeUtil.getId());
            imClient.setFkAppid(imApplication.getId());
            imClient.setClientId(user.getId().toString());
            imClient.setHeadPortrait(user.getHeadPortrait());
            imClient.setNickname(user.getNickname());
            imClientService.save(imClient);
            ImClientDevice imClientDevice = new ImClientDevice();
            imClientDevice.setId(SnowflakeUtil.getId());
            imClientDevice.setFkAppid(imApplication.getId());
            imClientDevice.setFkClientId(imClient.getId());
            imClientDevice.setValid(1);
            imClientDevice.setDeviceType(param.getDeviceType());
            imClientDevice.setCreateTime(new Date());
            imClientDevice.setUpdateTime(new Date());
            imClientDeviceService.save(imClientDevice);
        }

        LoginSuccessDto loginSuccessDto = new LoginSuccessDto();
        BeanUtils.copyProperties(user, loginSuccessDto);
        loginSuccessDto.setUserId(user.getId().toString());
        return loginSuccessDto;
    }

    /**
     * 重置密码
     *
     * @param param
     */
    public void resetPassword(ResetPasswordParam param) {
        String key = new StringBuilder(RedisKeyPrefixConstant.VERIFY_CODE_PREFIX).append(VerifySceneEnum.RESET_PWD.getCode()).append(param.getPhone()).toString();
        this.verifySMSVerifyCode(param.getVerifyCode(), key);
        User user = this.getOne(new QueryWrapper<User>().lambda()
                .eq(User::getPhone, param.getPhone()));
        if (user == null) {
            throw new BusinessException("查无此用户");
        }
        user.setPassword(AesUtil.encrypt(param.getPassword()));
        this.updateById(user);
    }

    /**
     * 更换手机号码
     *
     * @param param
     */
    public void changePhone(ChangePhoneParam param) {
        String key = new StringBuilder(RedisKeyPrefixConstant.VERIFY_CODE_PREFIX).append(VerifySceneEnum.CHANGE_PHONE.getCode()).append(param.getPhone()).toString();
        this.verifySMSVerifyCode(param.getVerifyCode(), key);
        User user = this.getOne(new QueryWrapper<User>().lambda()
                .eq(User::getId, param.getUserId()));
        if (user == null) {
            throw new BusinessException("查无此用户");
        }
        if (param.getPhone().equals(user.getPhone())) {
            throw new BusinessException("更换后的手机号码与当前手机号码一致，无需更换");
        }
        User userExist = this.getOne(new QueryWrapper<User>().lambda()
                .ne(User::getId, param.getUserId())
                .eq(User::getPhone, param.getPhone()));
        if (userExist != null) {
            throw new BusinessException("新手机号码已被注册");
        }
        user.setPhone(param.getPhone());
        this.updateById(user);
    }

    /**
     * 查找本人信息
     */
    public UserBaseDto myInfo() {
        ImClient currentClient = imClientService.getCurrentClient();
        User user = this.getOne(new QueryWrapper<User>().lambda()
                .eq(User::getId, currentClient.getClientId()));
        if (user == null) {
            throw new BusinessException("账号不存在");
        }
        UserBaseDto userBaseDto = new UserBaseDto();
        BeanUtils.copyProperties(user, userBaseDto);
        userBaseDto.setUserId(user.getId().toString());
        userBaseDto.setPhone(user.getPhone());
        return userBaseDto;
    }

    /**
     * 查找用户
     *
     * @param param
     */
    public UserBaseDto findUser(FindUserParam param) {
        User user = this.getOne(new QueryWrapper<User>().lambda()
                .eq(User::getPhone, param.getQueryStr())
                .or()
                .eq(User::getIdNumber, param.getQueryStr()));
        if (user == null) {
            throw new BusinessException("账号不存在");
        }
        UserBaseDto userBaseDto = new UserBaseDto();
        BeanUtils.copyProperties(user, userBaseDto);
        userBaseDto.setUserId(user.getId().toString());

        userBaseDto.setRelation(RelationEnum.STRANGER.getCode());
        // 查询该好友与自己关系： 陌生人、好友、被拉黑名单
        ImClient currentClient = imClientService.getCurrentClient();
        ImFriend imFriend = imFriendService.getByKey(currentClient.getClientId(), user.getId().toString());
        if (imFriend != null && FriendStateEnum.CONFORM.getCode().equals(imFriend.getState())) {
            userBaseDto.setRelation(RelationEnum.FRIEND.getCode());
            userBaseDto.setFriendName(imFriend.getFriendName());
        }
        // 查询是否被拉黑
        if (imClientBlacklistService.isBeBlack(currentClient.getClientId(), user.getId().toString())) {
            userBaseDto.setRelation(RelationEnum.BE_BLACK.getCode());
        }
        return userBaseDto;
    }

    /**
     * 查找用户
     *
     * @param param
     */
    public UserBaseDto getUserByUserId(GetUserParam param) {
        User user = this.getOne(new QueryWrapper<User>().lambda()
                .eq(User::getId, param.getUserId()));
        if (user == null) {
            throw new BusinessException("账号不存在");
        }
        UserBaseDto userBaseDto = new UserBaseDto();
        BeanUtils.copyProperties(user, userBaseDto);
        userBaseDto.setUserId(user.getId().toString());
        userBaseDto.setRelation(RelationEnum.STRANGER.getCode());
        // 查询该好友与自己关系： 陌生人、好友、被拉黑名单
        ImClient currentClient = imClientService.getCurrentClient();
        ImFriend imFriend = imFriendService.getByKey(currentClient.getClientId(), user.getId().toString());
        if (imFriend != null && FriendStateEnum.CONFORM.getCode().equals(imFriend.getState())) {
            userBaseDto.setRelation(RelationEnum.FRIEND.getCode());
        }
        // 查询是否被拉黑
        if (imClientBlacklistService.isBeBlack(currentClient.getClientId(), user.getId().toString())) {
            userBaseDto.setRelation(RelationEnum.BE_BLACK.getCode());
        }
        return userBaseDto;
    }

    /**
     * 修改头像
     *
     * @param param
     */
    @Transactional(rollbackFor = Exception.class)
    public void modifyHeadPortrait(ModifyHeadPortraitParam param) {
        User user = this.getById(param.getUserId());
        if (user == null) {
            throw new BusinessException("查无用户");
        }
        user.setHeadPortrait(param.getHeadPortrait());
        this.updateById(user);
        ImClientHeadPortraitAdd imClientHeadPortraitAdd = new ImClientHeadPortraitAdd();
        imClientHeadPortraitAdd.setHeadPortrait(param.getHeadPortrait());
        imClientService.updateHeadPortrait(imClientHeadPortraitAdd);
    }

    /**
     * 修改昵称
     *
     * @param param
     */
    @Transactional(rollbackFor = Exception.class)
    public void modifyNickname(ModifyNicknameParam param) {
        User user = this.getById(param.getUserId());
        if (user == null) {
            throw new BusinessException("查无用户");
        }
        if (param.getNickname().length() > NumberConstant.NUM_30) {
            throw new BusinessException("昵称长度需小于30位");
        }
        user.setNickname(param.getNickname());
        this.updateById(user);
        ImClientNicknameUpdate imClientNicknameUpdate = new ImClientNicknameUpdate();
        imClientNicknameUpdate.setNickname(param.getNickname());
        imClientService.updateNickname(imClientNicknameUpdate);
    }

    /**
     * 修改昵称
     *
     * @param param
     */
    public void modifyLandouNo(ModifyLandouParam param) {
        User user = this.getById(param.getUserId());
        if (user == null) {
            throw new BusinessException("查无用户");
        }
        User userByLando = this.getByLandouNo(param.getLandouNo());
        if (userByLando != null) {
            throw new BusinessException("该蓝豆号已被使用，请更换");
        }
        if (param.getLandouNo().length() > NumberConstant.NUM_32) {
            throw new BusinessException("蓝豆号输入长度需小于32位");
        }
        user.setIdNumber(param.getLandouNo());
        this.updateById(user);
    }

    /**
     * 修改性别
     *
     * @param param
     */
    public void modifySex(ModifySexParam param) {
        User user = this.getById(param.getUserId());
        if (user == null) {
            throw new BusinessException("查无用户");
        }
        user.setSex(param.getSex());
        this.updateById(user);
    }

    /**
     * 根据蓝豆号获取用户
     *
     * @param landouNo
     * @return
     */
    public User getByLandouNo(String landouNo) {
        return this.getOne(new QueryWrapper<User>().lambda()
                .eq(User::getIdNumber, landouNo));
    }

    /**
     * 校验短信验证码
     *
     * @param verifyCode
     * @param redisKey
     */
    private void verifySMSVerifyCode(String verifyCode, String redisKey) {
        String verifyCodeInRedis = redisUtils.getKey(redisKey);
        if (verifyCodeInRedis == null || !verifyCodeInRedis.equals(verifyCode)) {
            throw new BusinessException("短信验证码不正确");
        }
        redisUtils.delKey(redisKey);
    }

    /**
     * 获取可使用蓝豆号
     *
     * @return
     */
    private String getLandouNo() {
        String landouNo;
        do {
            landouNo = RandomUtil.generateRandomStr(8);
            User user = this.getByLandouNo(landouNo);
            if (user == null) {
                break;
            }
        } while (true);
        return landouNo;
    }
}
