package com.liquidnet.service.adam.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.common.exception.constant.ErrorCode;
import com.liquidnet.commons.lang.util.DESUtils;
import com.liquidnet.service.ResponseDto;
import com.liquidnet.service.adam.biz.AdamUserLoginBiz;
import com.liquidnet.service.adam.common.AdamErrorCode;
import com.liquidnet.service.adam.constant.AdamAdminisConstants;
import com.liquidnet.service.adam.constant.AdamRedisConstants;
import com.liquidnet.service.adam.constant.AdamUserConstants;
import com.liquidnet.service.adam.dto.base.AdamCurrentUser;
import com.liquidnet.service.adam.dto.base.AdamResultDto;
import com.liquidnet.service.adam.entity.AdamUser;
import com.liquidnet.service.adam.entity.AdamUserAuthLog;
import com.liquidnet.service.adam.mapper.AdamUserMapper;
import com.liquidnet.service.adam.service.*;
import com.liquidnet.service.adam.service.sys.IAdamSysEmailService;
import com.liquidnet.service.adam.service.sys.IAdamSysMessageService;
import com.liquidnet.service.adam.service.sys.IAdamSystemService;
import com.liquidnet.service.adam.util.EmailUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 用户信息 服务实现类
 * </p>
 *
 * @author liquidnet
 * @since 2020-09-16
 */
@Slf4j
@Service
public class AdamUserEmailServiceImpl implements IAdamUserEmailService {

    @Value("${liquidnet.conf.des.key}")
    private String desKey;

    @Value("${liquidnet.conf.webUrl}")
    private String webUrl;

    @Autowired
    private IAdamUserAuthLogService adamUserAuthLogService;

    @Autowired
    private IAdamSysEmailService adamSysEmailService;

    @Autowired
    private IAdamSystemService adamSystemService;

    @Autowired
    private IAdamUserService adamUserService;

    @Autowired
    private IAdamUserPasswordService adamUserPasswordService;

    @Autowired
    private AdamUserMapper adamUserMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private AdamUserLoginBiz adamUserLoginBiz;

    @Autowired
    private IAdamAdminisProcessHisService adamAdminisProcessHisService;

    @Override
    public String userEmailAuth(AdamUser adamUser) throws Exception {
        AdamUserAuthLog adamUserAuthLog = new AdamUserAuthLog();
        adamUserAuthLog.setUserId(adamUser.getId());
        adamUserAuthLog.setAuthResult(AdamUserConstants.UserAuthResultEnum.PENDING.name());
        adamUserAuthLog.setAuthType(AdamUserConstants.UserAuthTypeEnum.EMAIL.name());
        adamUserAuthLogService.insert(adamUserAuthLog);
        return sendRegisterEmail(adamUser);
    }

    @Override
    public String userEmailAuthForInvite(AdamUser adamUser, boolean isFirst) throws Exception {
        if (isFirst) {
            AdamUserAuthLog adamUserAuthLog = new AdamUserAuthLog();
            adamUserAuthLog.setUserId(adamUser.getId());
            adamUserAuthLog.setAuthResult(AdamUserConstants.UserAuthResultEnum.PENDING.name());
            adamUserAuthLog.setAuthType(AdamUserConstants.UserAuthTypeEnum.EMAIL.name());
            adamUserAuthLogService.insert(adamUserAuthLog);
        } else {
            UpdateWrapper<AdamUserAuthLog> updateWrapper = new UpdateWrapper<>();
            updateWrapper.eq("user_id", adamUser.getId());
            updateWrapper.eq("auth_type", AdamUserConstants.UserAuthTypeEnum.EMAIL.name());
            updateWrapper.set("auth_result", AdamUserConstants.UserAuthResultEnum.PENDING.name());
            updateWrapper.set("update_time", LocalDateTime.now());
            adamUserAuthLogService.update(updateWrapper);
        }
        return sendInviteEmail(adamUser);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public AdamResultDto<Object> confirmEmail(String confirmKey) throws Exception {
        AdamResultDto<Object> dto = this.decryptVerify(confirmKey);
        if (!dto.isSuccess()) {
            return dto;
        }
        String userId = (String) dto.getData();

        AdamUser adamUser = adamUserMapper.selectById(userId);
        if (null == adamUser) {
            return AdamResultDto.failure(AdamErrorCode.EMAIL_NOT_REGIS);
        }
        int i = adamUserMapper.updateById(adamUser);
        if (i != 1) {
            throw new RuntimeException();
        }
        redisUtil.del(AdamRedisConstants.USER_EMAIL_CONFIRM_KEY + userId);

        UpdateWrapper<AdamUserAuthLog> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("user_id", adamUser.getId());
        updateWrapper.eq("auth_type", AdamUserConstants.UserAuthTypeEnum.EMAIL.name());
        updateWrapper.set("auth_result", AdamUserConstants.UserAuthResultEnum.SUCCESS.name());
        adamUserAuthLogService.update(updateWrapper);

        //设置userToken信息中的
        adamUserLoginBiz.refreshUserCacheByEmailChange(adamUser.getId());

        return dto;
    }

    @Override
    public AdamResultDto resendEmail(String email) throws Exception {
        AdamUser adamUser = adamUserService.getUserByEmail(email);
        if (null == adamUser) {
            return AdamResultDto.failure(AdamErrorCode.EMAIL_NOT_REGIS);
        }
        String authUrl = sendRegisterEmail(adamUser);
        return AdamResultDto.success(authUrl);
    }

    @Override
    public AdamResultDto updateVerifyCodeSend(String userId, String newEmail, String password) {

        // 验证邮箱格式
        AdamResultDto checkEmail = EmailUtil.checkEmailFormat(newEmail);
        if (!checkEmail.isSuccess()) {
            return checkEmail;
        }
        // 验证邮箱是否注册
        AdamUser userByEmail = adamUserService.getUserByEmail(newEmail);
        if (userByEmail != null) {
            return AdamResultDto.failure(AdamErrorCode.ADAM_USER_EMAIL_REGISTERED);
        }

        // 验证用户
        AdamUser adamUser = adamUserService.selectById(userId);
        if (adamUser == null) {
            return AdamResultDto.failure(AdamErrorCode.ADAM001_PARAM_ERROR);
        }
        // 验证用户密码
        if (!adamUserPasswordService.getPasswordMD5(password).equals(adamUser.getPassword())) {
            return AdamResultDto.failure(AdamErrorCode.ADAM_USER_PW_0020016);
        }
        String redisKey = AdamRedisConstants.USER_EMAIL_UPDATE_KEY + adamUser.getId();
        // 获取redis key是否存在
//        Object o = redisUtil.get(redisKey);
//        if (o != null) {
//            return AdamResultDto.result(AdamErrorCode.SUCCESS.getCode(), AdamErrorCode.ADAM001_VERIFYCODE_SENT.getVal(), null);
//        }

        String verifyCode = adamSystemService.generateVerifyCode();
        String redisValue = verifyCode + "-" + newEmail;
        // 设置redis key 验证码过期时间
        redisUtil.set(redisKey, redisValue, TimeUnit.MINUTES.toSeconds(2));
        log.info("send change email,userId=[{}],newEmail=[{}],verifyCode=[{}]", adamUser.getId(), newEmail, verifyCode);

        String title = "Confirm You New Email";
        String emailMsg = "Hi {0}</br>," +
                "Please confirm that you want to change your company email address(Login ID) to {1}</br></br></br>" +
                "Verification code [{2}],you are using Smartnet to change email address,the code is effective within 5 minutes.</br></br>" +
                "If you didn't ask us to make this change,please feel free to ignore this email.</br>";
        emailMsg = MessageFormat.format(emailMsg, adamUser.getLastName(), newEmail, verifyCode);

        // 新旧邮箱都发送验证码
        adamSysEmailService.asyncSendEmail(newEmail, emailMsg, title);
        adamSysEmailService.asyncSendEmail(adamUser.getEmail(), emailMsg, title);

        return AdamResultDto.success();
    }

    @Override
    public AdamResultDto updateVerifyCodeConfirm(AdamCurrentUser currentUser, String verifyCode) {
        String redisKey = AdamRedisConstants.USER_EMAIL_UPDATE_KEY + currentUser.getId();
        // 获取redis key是否存在
        Object o = redisUtil.get(redisKey);
        if (o == null) {
            return AdamResultDto.failure(AdamErrorCode.ADAM001_VERIFYCODE_EXPIRED);
        }
        String value = o.toString();
        int i = value.indexOf("-");

        String redisVerifyCode = value.substring(0, i);
        String redisNewEmail = value.substring(i + 1);
        if (!redisVerifyCode.equals(verifyCode)) {
            return AdamResultDto.failure(AdamErrorCode.ADAM001_VERIFYCODE_ERROR);
        }
        // 验证邮箱是否注册
        AdamUser userByEmail = adamUserService.getUserByEmail(redisNewEmail);
        if (userByEmail != null) {
            return AdamResultDto.failure(AdamErrorCode.ADAM_USER_EMAIL_REGISTERED);
        }

        redisUtil.del(redisKey);
        AdamUser adamUser = new AdamUser();
        adamUser.setId(currentUser.getId());
        adamUser.setEmail(redisNewEmail);
        adamUser.setUpdateTime(LocalDateTime.now());
        boolean rst = adamUserService.updateById(adamUser) > 0;

        if (rst) {// Record process history
            adamAdminisProcessHisService.record(AdamAdminisConstants.ProcessHisEnum.MY_PROFILE_CHANGE_BP, null,
                    new String[]{currentUser.getComId(), currentUser.getId(), currentUser.getId(), currentUser.getName()});
        }

        return rst ? AdamResultDto.success() : AdamResultDto.failure(AdamErrorCode.ADAM001_OPERATION_FAILED);
    }

    public AdamResultDto<Object> decryptVerify(String encrypt) {
        try {
            DESUtils desUtils = new DESUtils(desKey);
            String decrypt = desUtils.decrypt(encrypt);

            String[] split = decrypt.split("_");
            if (split.length < 2) {
                return AdamResultDto.failure(AdamErrorCode.INVALID_AUTH_ADDRESS);
            }

            String redisKey = AdamRedisConstants.USER_EMAIL_CONFIRM_KEY + split[0];
            String emailConfirm = (String) redisUtil.get(redisKey);
            if (StringUtils.isEmpty(emailConfirm) || !emailConfirm.equalsIgnoreCase(split[1])) {
                return AdamResultDto.failure(AdamErrorCode.TOKEN_EXPIRED_OR_BROKEN);
            }
            return AdamResultDto.success(split[0]);
        } catch (Exception e) {
            log.error("user email decrypt verify error", e);
            return AdamResultDto.failure(AdamErrorCode.ADAM001_SYSTEM_ERROR);
        }
    }

    @Override
    public ResponseDto<?> confirmEmailByInvite(String key, String pw) {
        AdamResultDto<Object> resultDto = this.decryptVerify(key);
        if (!resultDto.isSuccess()) {
            log.warn("Email invitation link verification failed. key:{}", key);
            return ResponseDto.failure(AdamErrorCode.INVALID_AUTH_ADDRESS.getCode(), AdamErrorCode.INVALID_AUTH_ADDRESS.getVal());
        }
        String userId = (String) resultDto.getData();

        AdamUser adamUser = adamUserMapper.selectById(userId);
        if (null == adamUser) {
            log.warn("Email invitation link activation failed. userId[{}] not exist", userId);
            return ResponseDto.failure(AdamErrorCode.EMAIL_NOT_REGIS.getCode(), AdamErrorCode.EMAIL_NOT_REGIS.getVal());
        }
        if (!AdamUserConstants.UserStatusEnum.INVITED.getCode().equals(adamUser.getStatus())) {
            boolean isActive = AdamUserConstants.UserStatusEnum.ACTIVE.getCode().equals(adamUser.getStatus());
            AdamErrorCode adamErrorCode = isActive ? AdamErrorCode.ADAM_USER_HAS_BEEN_ACTIVATED : AdamErrorCode.ADAM_USER_HAS_BEEN_REMOVED;
            log.warn("Email invitation link activation failed. userId[{}] {}", userId, adamErrorCode.getVal());
            return ResponseDto.failure(adamErrorCode.getCode(), adamErrorCode.getVal());
        }

        AdamResultDto<?> checkPasswordFormat = adamUserPasswordService.checkPasswordFormat(pw);
        if (!checkPasswordFormat.isSuccess()) {
            log.warn("Email invitation link activation failed. userId[{}] {}", userId, checkPasswordFormat.getMessage());
            return AdamResultDto.getResponseDto(checkPasswordFormat);
        }

        AdamUser updateObj = new AdamUser();
        updateObj.setId(userId);
        updateObj.setPassword(adamUserPasswordService.getPasswordMD5(pw));
        updateObj.setStatus(AdamUserConstants.UserStatusEnum.ACTIVE.getCode());
        boolean updateFailed = adamUserService.updateById(updateObj) <= 0;

        if (!updateFailed) {
            UpdateWrapper<AdamUserAuthLog> updateWrapper = new UpdateWrapper<>();
            updateWrapper.eq("user_id", adamUser.getId());
            updateWrapper.eq("auth_type", AdamUserConstants.UserAuthTypeEnum.EMAIL.name());
            updateWrapper.set("auth_result", AdamUserConstants.UserAuthResultEnum.SUCCESS.name());
            updateWrapper.set("update_time", LocalDateTime.now());
            adamUserAuthLogService.update(updateWrapper);

            redisUtil.del(AdamRedisConstants.USER_EMAIL_CONFIRM_KEY + userId);
        }
        return updateFailed ? ResponseDto.failure(ErrorCode.RESPONSE_ERROE_BIZ) : ResponseDto.success();
    }

    /**
     * 注册邮箱,发送链接邮件
     *
     * @return url超链接
     */
    public String sendRegisterEmail(AdamUser adamUser) throws Exception {
        String confirmTag = UUID.randomUUID().toString();
        DESUtils desUtils = new DESUtils(desKey);
        String redisKey = AdamRedisConstants.USER_EMAIL_CONFIRM_KEY + adamUser.getId();
        redisUtil.set(redisKey, confirmTag, TimeUnit.MINUTES.toSeconds(30));
        String encrypt = desUtils.encrypt(adamUser.getId() + "_" + confirmTag);
        log.info("[SEND CONFIRM EMAIL] UserId : {}, confirm tag : {}", adamUser.getId(), encrypt);
        String url = webUrl + "/#/registerConfirm?key=" + encrypt;
        String emailMsg = "Please click the link below to complete sign up " +
                "</br>" + url;
        adamSysEmailService.asyncSendEmail(adamUser.getEmail(), emailMsg, "Confirmation of registration");
        return url;
    }

    /**
     * invite user email
     */
    public String sendInviteEmail(AdamUser adamUser) throws Exception {
        String confirmTag = UUID.randomUUID().toString();
        DESUtils desUtils = new DESUtils(desKey);
        String redisKey = AdamRedisConstants.USER_EMAIL_CONFIRM_KEY + adamUser.getId();
        redisUtil.set(redisKey, confirmTag, TimeUnit.HOURS.toSeconds(72));

        String encrypt = desUtils.encrypt(adamUser.getId() + "_" + confirmTag);

        log.info("[SEND INVITE EMAIL] UserId:{}, confirm tag:{}", adamUser.getId(), encrypt);

        String emailMsg = "Hi, Please click the link below to complete sign up " +
                "</br>" + webUrl + "/#/forgetPassword?key=" + encrypt;

        adamSysEmailService.asyncSendEmail(adamUser.getEmail(), emailMsg, "Confirmation of registration");

        return emailMsg;
    }

}

