package com.quantgroup.asset.distribution.service.funding.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.quantgroup.asset.distribution.enums.funding.AuditTargetEnum;
import com.quantgroup.asset.distribution.enums.funding.AuditTypeEnum;
import com.quantgroup.asset.distribution.enums.response.FundModuleResponse;
import com.quantgroup.asset.distribution.exception.QGException;
import com.quantgroup.asset.distribution.exception.QGExceptionType;
import com.quantgroup.asset.distribution.model.entity.fund.ChannelFundConfigNew;
import com.quantgroup.asset.distribution.model.response.GlobalResponse;
import com.quantgroup.asset.distribution.service.approval.IApprovalLogService;
import com.quantgroup.asset.distribution.service.funding.IFundModuleChannelFundConfigNewService;
import com.quantgroup.asset.distribution.service.jpa.entity.ApprovalLog;
import com.quantgroup.asset.distribution.service.jpa.entity.FundModuleChannelFundConfig;
import com.quantgroup.asset.distribution.service.jpa.entity.FundModuleChannelFundConfigNew;
import com.quantgroup.asset.distribution.service.jpa.repository.IFundModuleChannelFundConfigNewRepository;
import com.quantgroup.asset.distribution.util.fund.module.ChannelFundConfigUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.jpa.criteria.OrderImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
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.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.xml.transform.Source;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author : Hyuk
 * @description : FundModuleChannelFundConfigNewServiceImpl
 * @date : 2020/2/26 5:38 下午
 */
@Slf4j
@Service
public class FundModuleChannelFundConfigNewServiceImpl implements IFundModuleChannelFundConfigNewService {

    @Autowired
    private IFundModuleChannelFundConfigNewRepository fundModuleChannelFundConfigNewRepository;
    @Autowired
    private IApprovalLogService approvalLogService;
    @Autowired
    private EntityManager entityManager;

    @Override
    public Map<String, Object> getChannelFundConfigsByChannelOrFundId(String bizChannel, Long fundId, Integer pageNum, Integer pageSize) {
        // 分页条件
        Pageable pageable = new PageRequest(pageNum < 0 ? 0 : pageNum, pageSize);
        Specification<FundModuleChannelFundConfigNew> specification = new Specification<FundModuleChannelFundConfigNew>() {
            @Override
            public Predicate toPredicate(Root<FundModuleChannelFundConfigNew> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>();
                predicates.add(cb.equal(root.get("enable"), true));
                if(StringUtils.isNotEmpty(bizChannel)){
                    predicates.add(cb.equal(root.get("bizChannel"), bizChannel));
                }
                if(fundId != null){
                    predicates.add(cb.like(root.get("fundIds"), "%" + fundId + "%"));
                }
                query.where(predicates.toArray(new Predicate[predicates.size()]));
                return query.getRestriction();
            }
        };
        Page<FundModuleChannelFundConfigNew> channelFundConfigs = fundModuleChannelFundConfigNewRepository.findAll(specification, pageable);
        Map<String, Object> result = new HashMap<>();
        result.put("total", channelFundConfigs.getTotalElements());
        result.put("pages", channelFundConfigs.getTotalPages());
        result.put("pageSize", channelFundConfigs.getSize());
        result.put("pageNum", channelFundConfigs.getNumber());
        result.put("list", channelFundConfigs.getContent());
        return result;
    }

    @Transactional(rollbackFor=Exception.class)
    @Override
    public GlobalResponse addChannelFundConfig(String bizChannel, String funds, String remarks, String proposer, String auditor) {
        // 新增配置, 根据渠道查询如果库里已存在，返回异常;
        FundModuleChannelFundConfigNew fundModuleChannelFundConfigNew = findByBizChannel(bizChannel);
        if (fundModuleChannelFundConfigNew != null) {
            log.info("资方模块, 渠道 : {}资方配置已存在, 添加失败!", bizChannel);
            return GlobalResponse.create(FundModuleResponse.CHANNEL_FUND_CONFIG_IS_EXIST);
        }
        // 渠道资方配置是否正在审核
        if (approvalLogService.isAuditing(bizChannel)) {
            log.info("资方模块, 渠道 : {} 资方配置正在审批中, 不允许修改", bizChannel);
            return GlobalResponse.create(FundModuleResponse.CHANNEL_FUND_CONFIG_IS_AUDITING);
        }
        FundModuleChannelFundConfigNew newConfig = createdNewChannelFundConfigNew(bizChannel, funds, remarks);
        createdChannelFundConfigApprovalLog(bizChannel, proposer, auditor, null, newConfig.getId());
        return GlobalResponse.create(FundModuleResponse.SUCCESS);
    }

    @Transactional(rollbackFor=Exception.class)
    @Override
    public GlobalResponse updateChannelFundConfig(Long id, String bizChannel, String funds, String remarks, String proposer, String auditor) {
        // 更改配置, 根据id查询如果库里不存在，返回异常
        FundModuleChannelFundConfigNew configNew = fundModuleChannelFundConfigNewRepository.findByIdAndEnableIsTrue(id);
        if (configNew == null) {
            log.info("资方模块, 渠道 : {}, 配置id : {}, 资方配置不存在, 修改失败!", bizChannel, id);
            return GlobalResponse.create(FundModuleResponse.CHANNEL_FUND_CONFIG_NOT_EXIST);
        }
        // 如果该条配置在审批，不允许更改
        boolean isAuditing = approvalLogService.isAuditing(bizChannel);
        if (isAuditing) {
            log.info("资方模块, 渠道 : {}, 配置id : {}, 正在审批中, 不允许修改", bizChannel, id);
            return GlobalResponse.create(FundModuleResponse.CHANNEL_FUND_CONFIG_IS_AUDITING);
        }
        FundModuleChannelFundConfigNew newConfig = createdNewChannelFundConfigNew(bizChannel, funds, remarks);
        createdChannelFundConfigApprovalLog(bizChannel, proposer, auditor, id, newConfig.getId());
        return GlobalResponse.create(FundModuleResponse.SUCCESS);
    }

    @Cacheable(value="cacheManager", key="'ASSET_DISTRIBUTION:FUND_MODULE:CHANNEL_FUND_CONFIG:CK9A_'+#bizChannel")
    @Override
    public FundModuleChannelFundConfigNew findByBizChannel(String bizChannel) {
        List<FundModuleChannelFundConfigNew> configList = fundModuleChannelFundConfigNewRepository.findByBizChannelAndEnableIsTrue(bizChannel);
        if (CollectionUtils.isEmpty(configList)) { return null; }
        // 避免脏读
        if (configList.size() > 2) {
            throw new QGException(QGExceptionType.CHANNEL_FUND_CONFIG_GREATER_THAN_TOW, bizChannel);
        }
        Collections.sort(configList, (c1, c2) -> {
            // 别直接把相减把long类型转成int，可能越界
            if (c2.getId() > c1.getId()) {
                return 1;
            } else if (c2.getId() < c1.getId()) {
                return -1;
            } else {
                return 0;
            }
        });
        return configList.get(0);
    }

    @Override
    public FundModuleChannelFundConfigNew findById(Long id) {
        return fundModuleChannelFundConfigNewRepository.findById(id);
    }

    @Override
    public FundModuleChannelFundConfigNew auditPassConfig(Long preId, Long auditId) {
        FundModuleChannelFundConfigNew config = updateEnable(auditId, true);
        if (preId != null) {
            updateEnable(preId, false);
        }
        return config;
    }

    @CacheEvict(value = "cacheManager", key="'ASSET_DISTRIBUTION:FUND_MODULE:CHANNEL_FUND_CONFIG:CK9A_'+#bizChannel")
    @Override
    public void clearChannelFundConfigCache(String bizChannel) {
        log.info("渠道资方配置缓存清除, bizChannel : {}", bizChannel);
    }

    @Override
    public Map<String, Object> getChannelFundConfigsByChannelOrFundIdOrdered(String bizChannel, Long fundId, Integer pageNum, Integer pageSize) {
        List<BigInteger> ids = approvalLogService.getFundConfigIds(bizChannel);
        // 分页条件
        Integer start = pageNum * pageSize;
        Integer end = (pageNum + 1) * pageSize;
        List<FundModuleChannelFundConfigNew> channelFundConfigs;
        StringBuilder idsStr = new StringBuilder();
        for (BigInteger id : ids){
            idsStr.append(",").append(id);
        }
        idsStr.delete(0,1);
        String fundLike = "";
        if (fundId != null) {
            fundLike = "and fund_ids like '%" + fundId + "%'";
        }
        String sql = "SELECT * FROM fund_module_channel_fund_config_new where id in ("+idsStr.toString()+") "+ fundLike+" order by field(id,"+idsStr.toString()+") limit "+start+","+end;
        Query query = entityManager.createNativeQuery(sql,FundModuleChannelFundConfigNew.class);
        channelFundConfigs = query.getResultList();
        String total;
        if (fundId == null){
            total = ids.size() + "";
        }else {
            if (StringUtils.isEmpty(bizChannel)){
                total = fundModuleChannelFundConfigNewRepository.count(fundId,ids) + "";
            }else {
                total = fundModuleChannelFundConfigNewRepository.count(fundId,bizChannel,ids) + "";
            }
        }
        Map<String, Object> result = new HashMap<>();
        result.put("total", total);
        result.put("pages", (int)(new Double(total)%pageSize == 0?new Double(total)/pageSize:new Double(total)/pageSize +1));
        result.put("pageSize", pageSize);
        result.put("pageNum", pageNum);
        result.put("list", channelFundConfigs);
        return result;
    }

    @Override
    public List<String> getAllConfigChanels() {
        return fundModuleChannelFundConfigNewRepository.getAllBizChannel();
    }

    /**
     * 更改配置enable状态
     * @param id
     * @param enable
     */
    private FundModuleChannelFundConfigNew updateEnable(Long id, Boolean enable) {
        FundModuleChannelFundConfigNew config = fundModuleChannelFundConfigNewRepository.findById(id);
        config.setEnable(enable);
        return fundModuleChannelFundConfigNewRepository.save(config);
    }

    /**
     * 创建一条新配置记录
     * @param bizChannel
     * @param funds
     * @param remarks
     * @return
     */
    private FundModuleChannelFundConfigNew createdNewChannelFundConfigNew(String bizChannel, String funds, String remarks) {
        FundModuleChannelFundConfigNew config = new FundModuleChannelFundConfigNew();
        config.setBizChannel(bizChannel);
        config.setFunds(supplementDefaultFundsInfo(funds));
        config.setRemarks(remarks);
        config.setFundIds(ChannelFundConfigUtil.getAllFundIds(funds));
        config.setFundLimitTranslate(ChannelFundConfigUtil.getFundLimitTranslate(funds));
        config.setEnable(false);
        return fundModuleChannelFundConfigNewRepository.save(config);
    }

    /**
     *
     * @param funds
     * @return
     */
    private String supplementDefaultFundsInfo(String funds) {
        List<ChannelFundConfigNew> channelFundConfigNewList = JSONArray.parseArray(funds, ChannelFundConfigNew.class);
        channelFundConfigNewList.forEach(channelFundConfigNew -> {
            if (channelFundConfigNew.getFeeType() == null) {
                channelFundConfigNew.setFeeType(1);
            }
            if (channelFundConfigNew.getRateType() == null) {
                channelFundConfigNew.setRateType(1);
            }
            if (StringUtils.isEmpty(channelFundConfigNew.getRate())) {
                channelFundConfigNew.setRate("0");
            }
        });
        return JSON.toJSONString(channelFundConfigNewList);
    }

    /**
     * 创建渠道资方配置审批记录
     * @param bizChannel
     * @param proposer
     * @param auditor
     * @param preConfigId
     * @param auditConfigId
     */
    private void createdChannelFundConfigApprovalLog(String bizChannel, String proposer, String auditor, Long preConfigId, Long auditConfigId) {
        approvalLogService.createApprovalLog(AuditTypeEnum.ONLINE, AuditTargetEnum.FUND_CONFIG, bizChannel, proposer, auditor, preConfigId, auditConfigId);
    }
}
