package cn.quantgroup.xyqb.service.user.impl;

import cn.quantgroup.xyqb.model.ModifyPhoneNoApplyStatusEnum;
import cn.quantgroup.xyqb.model.ModifyPhoneNoProcessingStatusEnum;
import cn.quantgroup.xyqb.aspect.limit.AccessLimit;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.AuditReq;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.ModifyPhoneNoQueryReq;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.Step1Req;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.Step2Req;
import cn.quantgroup.xyqb.controller.modifyphoneno.resp.ProgressResp;
import cn.quantgroup.xyqb.entity.ModifyPhoneNo;
import cn.quantgroup.xyqb.exception.DataException;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.repository.IModifyPhoneNoRepository;
import cn.quantgroup.xyqb.repository.IUserDetailRepository;
import cn.quantgroup.xyqb.repository.IUserRepository;
import cn.quantgroup.xyqb.service.http.IHttpService;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.service.user.IModifyPhoneNoService;
import cn.quantgroup.xyqb.service.user.IUserService;
import cn.quantgroup.xyqb.util.DateUtils;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.qiniu.util.Auth;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.persistence.criteria.*;
import java.util.Date;
import java.util.List;

/**
 * Date: 2019/11/4
 * Time: 下午2:59
 *
 * @author: yangrui
 */
@Slf4j
@Service
public class ModifyPhoneNoServiceImpl implements IModifyPhoneNoService {

    @Resource
    private IModifyPhoneNoRepository modifyPhoneNoRepository;
    @Resource
    private IUserDetailRepository userDetailRepository;
    @Resource
    private ISmsService smsService;
    @Resource
    private IHttpService httpService;
    @Resource
    private IUserService userService;
    @Resource
    private IUserRepository userRepository;

    @Value("${api.https}")
    public String apiHttps;
    @Value("${qiniu.storage.accessKey}")
    public String accessKey;
    @Value("${qiniu.storage.secretKey}")
    public String secretKey;

    /**
     * @param userId   user.id
     * @param step1Req
     * @return
     */
    @Override
    @AccessLimit(redisKey = "saveStep1")
    @Transactional(rollbackFor = Exception.class)
    public Long saveStep1(Long userId, Step1Req step1Req) {
        allowModify4Step1(userId, step1Req.getName(), step1Req.getIdCard(), step1Req.getPrevPhoneNo(), step1Req.getCurPhoneNo(),
                step1Req.getSmsCode());
        ModifyPhoneNo modifyPhoneNo = Step1Req.adapt(step1Req);
        modifyPhoneNo.setUserId(userId);
        modifyPhoneNoRepository.saveAndFlush(modifyPhoneNo);
        return modifyPhoneNo.getId();
    }

    /**
     * @param step2Req
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveStep2(Step2Req step2Req) {
        ModifyPhoneNo modifyPhoneNo = modifyPhoneNoRepository.findOne(step2Req.getId());
        if (modifyPhoneNo == null) {
            throw new DataException("数据不存在。");
        }
        modifyPhoneNo.setIdCardFaceUrl(step2Req.getIdCardFaceUrl());
        modifyPhoneNo.setIdCardRearUrl(step2Req.getIdCardRearUrl());
        modifyPhoneNo.setIdCardHoldUrl(step2Req.getIdCardHoldUrl());
        modifyPhoneNo.setApplyStatus(ModifyPhoneNoApplyStatusEnum.INIT.ordinal());
        modifyPhoneNo.setProcessingStatus(ModifyPhoneNoProcessingStatusEnum.INIT.ordinal());
    }

    /**
     * 查询用户手机号修改进度
     *
     * @param userId user.id
     * @return
     */
    @Override
    public ProgressResp progress(Long userId) {
        ProgressResp progressResp = new ProgressResp();
        ModifyPhoneNo modifyPhoneNo = modifyPhoneNoRepository.findFirstByUserIdAndApplyStatus(userId, ModifyPhoneNoApplyStatusEnum.INIT.ordinal());
        if (modifyPhoneNo == null) {
            progressResp.setProgress(0);
            return progressResp;
        }
        if (StringUtils.isBlank(modifyPhoneNo.getIdCardFaceUrl()) && StringUtils.isBlank(modifyPhoneNo.getIdCardRearUrl()) && StringUtils.isBlank(modifyPhoneNo.getIdCardHoldUrl())) {
            progressResp.setProgress(1);
            progressResp.setId(modifyPhoneNo.getId());
            return progressResp;
        }
        if (StringUtils.isNotBlank(modifyPhoneNo.getIdCardFaceUrl()) && StringUtils.isNotBlank(modifyPhoneNo.getIdCardRearUrl()) && StringUtils.isNotBlank(modifyPhoneNo.getIdCardHoldUrl())) {
            progressResp.setProgress(2);
            return progressResp;
        }
        progressResp.setProgress(-1);
        return progressResp;
    }

    @Override
    public Page<ModifyPhoneNo> list(ModifyPhoneNoQueryReq modifyPhoneNoQueryReq) {
        Sort.Order order = new Sort.Order(Sort.Direction.DESC, "id");
        Sort sort = new Sort(order);
        Pageable pageable = new PageRequest(modifyPhoneNoQueryReq.getPage() - 1, modifyPhoneNoQueryReq.getSize(), sort);

        Specification<ModifyPhoneNo> specification = new Specification<ModifyPhoneNo>() {
            @Override
            public Predicate toPredicate(Root<ModifyPhoneNo> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> list = Lists.newArrayList();
                if (StringUtils.isNotBlank(modifyPhoneNoQueryReq.getPhoneNo())) {
                    list.add(criteriaBuilder.equal(root.get("prevPhoneNo"), modifyPhoneNoQueryReq.getPhoneNo()));
                }
                if (StringUtils.isNotBlank(modifyPhoneNoQueryReq.getStartAt())) {
                    list.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), DateUtils.strToDate(modifyPhoneNoQueryReq.getStartAt() + " 00:00:00", DateUtils.YMD_HMS_FORMAT)));
                }
                if (StringUtils.isNotBlank(modifyPhoneNoQueryReq.getEndAt())) {
                    list.add(criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), DateUtils.strToDate(modifyPhoneNoQueryReq.getEndAt() + " 23:59:59", DateUtils.YMD_HMS_FORMAT)));
                }
                if (modifyPhoneNoQueryReq.getApplyStatus() != null) {
                    list.add(criteriaBuilder.equal(root.get("applyStatus"), modifyPhoneNoQueryReq.getApplyStatus().ordinal()));
                }
                if (modifyPhoneNoQueryReq.getProcessingStatus() != null) {
                    list.add(criteriaBuilder.equal(root.get("processingStatus"), modifyPhoneNoQueryReq.getProcessingStatus().ordinal()));
                }
                Predicate[] arr = new Predicate[list.size()];
                return criteriaBuilder.and(list.toArray(arr));
            }
        };

        Page<ModifyPhoneNo> page = modifyPhoneNoRepository.findAll(specification, pageable);
        List<ModifyPhoneNo> modifyPhoneNos = page.getContent();
        if (CollectionUtils.isNotEmpty(modifyPhoneNos)) {
            modifyPhoneNos.forEach(modifyPhoneNo -> {
                Auth auth = Auth.create(accessKey, secretKey);
                if (StringUtils.isNotBlank(modifyPhoneNo.getIdCardFaceUrl())) {
                    modifyPhoneNo.setIdCardFaceUrl(auth.privateDownloadUrl(modifyPhoneNo.getIdCardFaceUrl()));
                }
                if (StringUtils.isNotBlank(modifyPhoneNo.getIdCardRearUrl())) {
                    modifyPhoneNo.setIdCardRearUrl(auth.privateDownloadUrl(modifyPhoneNo.getIdCardRearUrl()));
                }
                if (StringUtils.isNotBlank(modifyPhoneNo.getIdCardHoldUrl())) {
                    modifyPhoneNo.setIdCardHoldUrl(auth.privateDownloadUrl(modifyPhoneNo.getIdCardHoldUrl()));
                }
            });
        }
        return page;
    }

    /**
     * @param id user_modify_phone_no.id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void feedback(Long id) {
        ModifyPhoneNo modifyPhoneNo = modifyPhoneNoRepository.findOne(id);
        if (modifyPhoneNo == null) {
            throw new DataException("数据不存在。");
        }
        modifyPhoneNo.setProcessingStatus(ModifyPhoneNoProcessingStatusEnum.DONE.ordinal());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void audit(AuditReq auditReq) {
        ModifyPhoneNo modifyPhoneNo = modifyPhoneNoRepository.findOne(auditReq.getId());
        if (modifyPhoneNo == null) {
            throw new DataException("数据不存在。");
        }
        if (ModifyPhoneNoApplyStatusEnum.NO_ALLOW == auditReq.getApplyStatus()) {
            modifyPhoneNo.setApplyStatus(ModifyPhoneNoApplyStatusEnum.NO_ALLOW.ordinal());
            modifyPhoneNo.setApplyStatusReason(auditReq.getApplyStatusReason());
            modifyPhoneNo.setProcessingStatus(ModifyPhoneNoProcessingStatusEnum.WAIT_4_USER_FEEDBACK.ordinal());
        }
        if (ModifyPhoneNoApplyStatusEnum.DONE == auditReq.getApplyStatus()) {
            log.info("audit userId = 【{}】, name = 【{}】, idCard = 【{}】, prevPhoneNo = 【{}】, " +
                    "curPhoneNo = 【{}】", modifyPhoneNo.getUserId(), modifyPhoneNo.getName(), modifyPhoneNo.getIdCard(), modifyPhoneNo.getPrevPhoneNo(), modifyPhoneNo.getCurPhoneNo());
            if (userDetailRepository.findByUserIdAndPhoneNoAndNameAndIdNo(modifyPhoneNo.getUserId(), modifyPhoneNo.getPrevPhoneNo(), modifyPhoneNo.getName(), modifyPhoneNo.getIdCard()) == null) {
                throw new DataException("姓名、身份证、电话号不符。");
            }
            if (userRepository.findByPhoneNo(modifyPhoneNo.getCurPhoneNo()) != null) {
                throw new DataException("新手机号已存在，不支持更换。");
            }
            checkLoanStatus(modifyPhoneNo.getUserId());
            userService.modifyPhoneNo(modifyPhoneNo.getPrevPhoneNo(), modifyPhoneNo.getCurPhoneNo());
            modifyPhoneNo.setApplyStatus(ModifyPhoneNoApplyStatusEnum.DONE.ordinal());
            modifyPhoneNo.setProcessingStatus(ModifyPhoneNoProcessingStatusEnum.WAIT_4_USER_FEEDBACK.ordinal());
        }
    }

    /**
     * 验证用户是否允许修改手机号
     * <p>
     * 校验用户填写的原手机号对应的用户姓名、用户身份证是否和用户中心已有信息一致。若不一致，不允许修改。
     * 新手机号是否在用户中心已存在，若已存在，不允许修改。
     * 用户有审核中和放款中的订单，不允许修改。
     * 用户有还款中的订单，不允许修改。
     * </p>
     *
     * @param userId      user.id
     * @param name        注册人真实姓名
     * @param idCard      注册人身份证件号
     * @param prevPhoneNo 当前手机号码
     * @param curPhoneNo  新手机号码
     * @param smsCode     新手机号码短信验证码
     * @return
     */
    public void allowModify4Step1(Long userId, String name, String idCard, String prevPhoneNo, String curPhoneNo, String smsCode) {
        log.info("allowModify4Step1 userId = 【{}】, name = 【{}】, idCard = 【{}】, prevPhoneNo = 【{}】, " +
                "curPhoneNo = 【{}】, smsCode = 【{}】", userId, name, idCard, prevPhoneNo, curPhoneNo, smsCode);
        if (modifyPhoneNoRepository.findFirstByUserIdAndApplyStatus(userId, ModifyPhoneNoProcessingStatusEnum.INIT.ordinal()) != null) {
            throw new DataException("已存在处理中的申请单，不支持再次更换。");
        }
        if (!smsService.verifyPhoneAndCode(curPhoneNo, smsCode)) {
            throw new DataException("验证码不正确。");
        }
        if (userDetailRepository.findByUserIdAndPhoneNoAndNameAndIdNo(userId, prevPhoneNo, name, idCard) == null) {
            throw new DataException("信息填写有误，请重新填写。");
        }
        if (userRepository.findByPhoneNo(curPhoneNo) != null) {
            throw new DataException("填写信息有误，新手机号已注册。");
        }
        checkLoanStatus(userId);
    }

    /**
     * @param userId user.id
     */
    public void checkLoanStatus(Long userId) {
        log.info("allowModify userId = 【{}】", userId);
        String res = httpService.get(apiHttps + "/ex/loan/user/loan_status.json?userId=" + userId);
        log.info("allowModify res = 【{}】", res);
        if (StringUtils.isBlank(res)) {
            throw new DataException("系统内部错误。");
        } else {
            try {
                JsonResult jsonResult = JSONObject.parseObject(res, JsonResult.class);
                if (jsonResult == null) {
                    throw new DataException("系统内部错误。");
                }
                if (!jsonResult.isSuccess() || !Boolean.parseBoolean(jsonResult.getData().toString())) {
                    throw new DataException(jsonResult.getMsg());
                }
            } catch (JSONException e) {
                log.error("allowModify4Audit e = 【{}】", e);
                throw new DataException("系统内部错误。");
            }
        }
    }
}
