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

import com.liquidnet.common.exception.LiquidnetServiceException;
import com.liquidnet.common.mq.constant.MQConst;
import com.liquidnet.commons.lang.util.*;
import com.liquidnet.service.adam.dto.AdamEntersParam;
import com.liquidnet.service.adam.dto.vo.AdamEntersVo;
import com.liquidnet.service.adam.service.IAdamEntersService;
import com.liquidnet.service.adam.service.AdamRdmService;
import com.liquidnet.service.base.ErrorMapping;
import com.liquidnet.service.base.SqlMapping;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReturnDocument;
import com.mongodb.client.result.DeleteResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;

import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

/**
 * <p>
 * 入场人 服务实现类
 * </p>
 *
 * @author liquidnet
 * @since 2021-04-28
 */
@Slf4j
@Service
public class AdamEntersServiceImpl implements IAdamEntersService {
    @Autowired
    Environment env;
    @Autowired
    MongoTemplate mongoTemplate;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    AdamRdmService adamRdmService;


    private static final String PHP_API_REAL_NAME = "/IdCardCheckVerification";

    @Override
//    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public String add(AdamEntersParam parameter) {
        String currentUid = CurrentUtil.getCurrentUid();
        LocalDateTime now = LocalDateTime.now();


        if (1 == parameter.getType()) {
            if (!adamRdmService.isCertification(1, parameter.getIdCard(), parameter.getName())) {
                identityHandler(currentUid, parameter.getName(), parameter.getIdCard());
            }
        }


        List<AdamEntersVo> vos = adamRdmService.getEntersVoByUid(currentUid);

        AdamEntersVo vo = AdamEntersVo.getNew();
        BeanUtils.copyProperties(parameter, vo);
        vo.setEntersId(String.valueOf(IDGenerator.nextSnowId()));
        vo.setUid(currentUid);
        vo.setIsDefault(CollectionUtils.isEmpty(vos));
        vo.setState(1);
        vo.setCreatedAt(now);

        long s = System.currentTimeMillis();
        mongoTemplate.insert(vo, AdamEntersVo.class.getSimpleName());
        log.debug("#MDB耗时:{}ms", System.currentTimeMillis() - s);

        s = System.currentTimeMillis();
        adamRdmService.delEntersVoByUid(currentUid);
        if (1 == parameter.getType()) {
            adamRdmService.setCertification(1, parameter.getIdCard(), parameter.getName());
        }
        log.debug("#RDS耗时:{}ms", System.currentTimeMillis() - s);

        s = System.currentTimeMillis();
        String msg = SqlMapping.get("adam_enters.add",
                vo.getEntersId(), vo.getUid(), vo.getType(), vo.getName(), vo.getMobile(), vo.getIdCard(), vo.getIsDefault(), vo.getState(), now
        );
        log.debug("#SQL.GET耗时:{}ms", System.currentTimeMillis() - s);

        s = System.currentTimeMillis();
        rabbitTemplate.convertAndSend(MQConst.EX_LNS_SQL_UCENTER, MQConst.RK_SQL_UCENTER,
                msg
        );
        log.debug("#MQ耗时:{}ms", System.currentTimeMillis() - s);
        return vo.getEntersId();
    }

    @Override
//    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void def(String uid, String entersId) {
        LocalDateTime now = LocalDateTime.now();

        LinkedList<Object[]> toMqObjs = new LinkedList<>();
        List<AdamEntersVo> vos = adamRdmService.getEntersVoByUid(uid);
        if (vos.size() > 1) {// 取消原默认
//            AdamEntersVo unDeaultVo = AdamEntersVo.getNew();
//            unDeaultVo.setIsDefault(false);
//            unDeaultVo.setUpdatedAt(now);
            long s = System.currentTimeMillis();
            Document doc = mongoTemplate.getCollection(AdamEntersVo.class.getSimpleName()).findOneAndUpdate(
                    Query.query(Criteria.where("uid").is(uid).and("state").is(1).and("isDefault").is(true)).getQueryObject(),
                    new Document("$set", new Document("isDefault", false).append("updatedAt", now)),
                    new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)
            );
            log.debug("#MDB耗时:{}ms", System.currentTimeMillis() - s);
            if (null != doc) {
                AdamEntersVo unDeaultVoAfter = BsonUtil.toBean(doc, AdamEntersVo.class);

                toMqObjs.add(new Object[]{false, now, unDeaultVoAfter.getEntersId()});

                vos = this.collectionProcess(vos, unDeaultVoAfter.getEntersId(), unDeaultVoAfter);
            }
        }
        {// 设置新默认
//            AdamEntersVo defaultVo = AdamEntersVo.getNew();
//            defaultVo.setIsDefault(true);
//            defaultVo.setUpdatedAt(now);
            long s = System.currentTimeMillis();
            Document doc = mongoTemplate.getCollection(AdamEntersVo.class.getSimpleName()).findOneAndUpdate(
                    Query.query(Criteria.where("uid").is(uid).and("entersId").is(entersId)).getQueryObject(),
                    new Document("$set", new Document("isDefault", true).append("updatedAt", now)),
                    new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)
            );
            log.debug("#MDB耗时:{}ms", System.currentTimeMillis() - s);
            if (null != doc) {
                AdamEntersVo defaultVoAfter = BsonUtil.toBean(doc, AdamEntersVo.class);

                toMqObjs.add(new Object[]{true, now, entersId});

                vos = this.collectionProcess(vos, defaultVoAfter.getEntersId(), defaultVoAfter);
            }
        }
        if (!CollectionUtils.isEmpty(toMqObjs)) {
            long s = System.currentTimeMillis();
            adamRdmService.setEntersVoByUid(uid, vos);
            log.debug("#RDS耗时:{}ms", System.currentTimeMillis() - s);
            s = System.currentTimeMillis();
            rabbitTemplate.convertAndSend(MQConst.EX_LNS_SQL_UCENTER, MQConst.RK_SQL_UCENTER,
                    SqlMapping.get("adam_enters.update.is_default", toMqObjs));
            log.debug("#MQ耗时:{}ms", System.currentTimeMillis() - s);
        }
    }

    @Override
//    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void edit(AdamEntersParam parameter) {
        LocalDateTime now = LocalDateTime.now();
        String currentUid = CurrentUtil.getCurrentUid();


        if (1 == parameter.getType()) {
            if (!adamRdmService.isCertification(1, parameter.getIdCard(), parameter.getName())) {
                identityHandler(currentUid, parameter.getName(), parameter.getIdCard());
            }
        }


        AdamEntersVo updateVo = AdamEntersVo.getNew();
        BeanUtils.copyProperties(parameter, updateVo);
        long s = System.currentTimeMillis();
        Document doc = mongoTemplate.getCollection(AdamEntersVo.class.getSimpleName()).findOneAndUpdate(
                Query.query(Criteria.where("entersId").is(parameter.getEntersId())).getQueryObject(),
                new Document("$set", Document.parse(JsonUtils.toJson(updateVo)).append("updatedAt", now)),
                new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)
        );
        log.debug("#MDB耗时:{}ms", System.currentTimeMillis() - s);
        if (null != doc) {
            updateVo = BsonUtil.toBean(doc, AdamEntersVo.class);

            List<AdamEntersVo> vos = adamRdmService.getEntersVoByUid(currentUid);
            s = System.currentTimeMillis();
            adamRdmService.setEntersVoByUid(currentUid, this.collectionProcess(vos, parameter.getEntersId(), updateVo));
            if (1 == parameter.getType()) {
                adamRdmService.setCertification(1, parameter.getIdCard(), parameter.getName());
            }
            log.debug("#RDS耗时:{}ms", System.currentTimeMillis() - s);

            s = System.currentTimeMillis();
            rabbitTemplate.convertAndSend(MQConst.EX_LNS_SQL_UCENTER, MQConst.RK_SQL_UCENTER,
                    SqlMapping.get("adam_enters.edit",
                            updateVo.getType(), updateVo.getName(), updateVo.getMobile(), updateVo.getIdCard(), updateVo.getIsDefault(), updateVo.getState(), now, updateVo.getEntersId()
                    )
            );
            log.debug("#MQ耗时:{}ms", System.currentTimeMillis() - s);
        }
    }

    @Override
//    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void remove(String uid, String entersId) {
        LocalDateTime now = LocalDateTime.now();
        long s = System.currentTimeMillis();
        DeleteResult deleteResult = mongoTemplate.remove(
                Query.query(Criteria.where("entersId").is(entersId)), AdamEntersVo.class.getSimpleName()
        );
        log.debug("#MDB耗时:{}ms", System.currentTimeMillis() - s);
        List<AdamEntersVo> vos = adamRdmService.getEntersVoByUid(uid);
        vos.removeIf(r -> r.getEntersId().equals(entersId));
        s = System.currentTimeMillis();
        adamRdmService.setEntersVoByUid(uid, vos);
        log.debug("#RDS耗时:{}ms", System.currentTimeMillis() - s);

        s = System.currentTimeMillis();
        rabbitTemplate.convertAndSend(MQConst.EX_LNS_SQL_UCENTER, MQConst.RK_SQL_UCENTER,
                SqlMapping.get("adam_enters.remove", now, now, entersId)
        );
        log.debug("#MQ耗时:{}ms", System.currentTimeMillis() - s);
    }

    @Override
    public AdamEntersVo queryDefault(String uid) {
        List<AdamEntersVo> vos = adamRdmService.getEntersVoByUid(uid);

        if (!CollectionUtils.isEmpty(vos)) {
            AdamEntersVo defaultVo = null;
            for (AdamEntersVo vo : vos) {
                if (vo.getIsDefault()) {
                    return vo;
                }
                defaultVo = null == defaultVo ? vo :
                        vo.getCreatedAt().compareTo(defaultVo.getCreatedAt()) > 0 ? vo : defaultVo;
            }
            return defaultVo;
        }
        return null;
    }

    /* ---------------------------------------------------------------  */

    private List<AdamEntersVo> collectionProcess(List<AdamEntersVo> vos, String replaceId, AdamEntersVo updateVo) {
        long s = System.currentTimeMillis();
        int idx = IntStream.range(0, vos.size())
                .filter(i -> vos.get(i).getEntersId().equals(replaceId))
                .findFirst().orElse(-1);
        if (idx == -1) {
            adamRdmService.delEntersVoByUid(updateVo.getUid());

            List<AdamEntersVo> entersVos = adamRdmService.getEntersVoByUid(updateVo.getUid());

            idx = IntStream.range(0, vos.size())
                    .filter(i -> entersVos.get(i).getEntersId().equals(replaceId))
                    .findFirst().orElse(-1);

            if (idx == -1) {
                ErrorMapping.ErrorMessage errorMessage = ErrorMapping.get("10017");
                throw new LiquidnetServiceException(errorMessage.getCode(), errorMessage.getMessage());
            }
        }
        vos.set(idx, updateVo);
        log.debug("#collect.process耗时:{}ms", System.currentTimeMillis() - s);
        return vos;
    }

    private void identityHandler(String currentUid, String name, String idCard) {
        String respStr = null;
        try {
            LinkedMultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
            paramsMap.add("realName", name);
            paramsMap.add("cardNo", idCard);

            long s = System.currentTimeMillis();
            respStr = HttpUtil.postToPhpApi(env.getProperty("liquidnet.url-service.url") + PHP_API_REAL_NAME, paramsMap);
            log.debug("#PHP.API耗时:{}ms", System.currentTimeMillis() - s);
            log.debug("###PHP.API[{}].RESP:{}", PHP_API_REAL_NAME, respStr);
            Map respMap = JsonUtils.fromJson(respStr, Map.class);
            if (CollectionUtils.isEmpty(respMap) || !StringUtils.equalsIgnoreCase("OK", (String) respMap.get("message"))) {
                log.warn("PHP.API实名认证失败[uid:{},name:{},idCard:{},respStr:{}]", currentUid, name, idCard, respStr);
                ErrorMapping.ErrorMessage errorMessage = ErrorMapping.get("10102");
                throw new LiquidnetServiceException(errorMessage.getCode(), errorMessage.getMessage());
            }
        } catch (Exception e) {
            log.error("PHP.API实名认证异常[uid:{},name:{},idCard:{},respStr:{}]", currentUid, name, idCard, respStr, e);
            ErrorMapping.ErrorMessage errorMessage = ErrorMapping.get("10102");
            throw new LiquidnetServiceException(errorMessage.getCode(), errorMessage.getMessage());
        }
    }
}
