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

import java.util.*;
import java.util.stream.Collectors;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.quantgroup.asset.distribution.model.entity.fund.ChannelFundConfig;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
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.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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.response.GlobalResponse;
import com.quantgroup.asset.distribution.service.approval.IApprovalLogService;
import com.quantgroup.asset.distribution.service.funding.IFundModuleChannelFundConfigService;
import com.quantgroup.asset.distribution.service.jpa.entity.FundModuleChannelFundConfig;
import com.quantgroup.asset.distribution.service.jpa.repository.IFundModuleChannelFundConfigRepository;
import com.quantgroup.asset.distribution.util.fund.module.ChannelFundConfigUtil;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class FundModuleChannelFundConfigServiceImpl implements IFundModuleChannelFundConfigService{
	
	@Autowired
	private IFundModuleChannelFundConfigRepository fundModuleChannelFundConfigRepository;
	@Autowired
	private IApprovalLogService approvalLogService;

	/**
	 * 分页使用
	 */
	@Override
	public Map<String, Object> getChannelFundConfigsByChannelOrFundId(String bizChannel, Long fundId,
			Integer pageNum, Integer pageSize) {
		// 分页条件
		Pageable pageable = new PageRequest(pageNum < 0 ? 0 : pageNum, pageSize);
		Specification<FundModuleChannelFundConfig> specification = new Specification<FundModuleChannelFundConfig>() {
			@Override
			public Predicate toPredicate(Root<FundModuleChannelFundConfig> 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<FundModuleChannelFundConfig> channelFundConfigs = fundModuleChannelFundConfigRepository.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) {
		// 新增配置, 根据渠道查询如果库里已存在，返回异常;
		FundModuleChannelFundConfig fundModuleChannelFundConfig = findByBizChannel(bizChannel);
		if (fundModuleChannelFundConfig != null) {
			log.info("资方模块, 渠道 : {}资方配置已存在, 添加失败!", bizChannel);
			return GlobalResponse.create(FundModuleResponse.CHANNEL_FUND_CONFIG_IS_EXIST);
		}
		FundModuleChannelFundConfig newConfig = createdNewChannelFundConfig(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查询如果库里不存在，返回异常
		FundModuleChannelFundConfig fundModuleChannelFundConfig = fundModuleChannelFundConfigRepository.findByIdAndEnableIsTrue(id);
		if (fundModuleChannelFundConfig == 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);
		}
		FundModuleChannelFundConfig auditConfig = createdNewChannelFundConfig(bizChannel, funds, remarks);
		createdChannelFundConfigApprovalLog(bizChannel, proposer, auditor, fundModuleChannelFundConfig.getId(), auditConfig.getId());
		return GlobalResponse.create(FundModuleResponse.SUCCESS);
	}
	
	@Cacheable(value="cacheManager", key="'ASSET_DISTRIBUTION:FUND_MODULE:CHANNEL_FUND_CONFIG:AC8A_'+#bizChannel")
	@Override
	public FundModuleChannelFundConfig findByBizChannel(String bizChannel) {
		List<FundModuleChannelFundConfig> configList = fundModuleChannelFundConfigRepository.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 FundModuleChannelFundConfig findById(Long id) {
		return fundModuleChannelFundConfigRepository.findById(id);
	}
	
	/**
	 * 增加一条配置记录，Enable先为false
	 * @param bizChannel
	 * @param funds
	 * @param remarks
	 * @return
	 */
	private FundModuleChannelFundConfig createdNewChannelFundConfig(String bizChannel, String funds, String remarks) {
		FundModuleChannelFundConfig fundModuleChannelFundConfig = new FundModuleChannelFundConfig();
		fundModuleChannelFundConfig.setBizChannel(bizChannel);
		fundModuleChannelFundConfig.setFunds(supplementDefaultFundsInfo(funds));
		fundModuleChannelFundConfig.setRemarks(remarks);
		fundModuleChannelFundConfig.setFundIds(ChannelFundConfigUtil.getAllFundIds(funds));
		// 默认测试
		fundModuleChannelFundConfig.setType(0);
		fundModuleChannelFundConfig.setEnable(false);
		return fundModuleChannelFundConfigRepository.save(fundModuleChannelFundConfig);
	}

	/**
	 * 默认补充资金方相关信息
	 * @param funds
	 * @return
	 */
	private String supplementDefaultFundsInfo(String funds) {
		List<ChannelFundConfig> channelFundConfigList = JSONArray.parseArray(funds, ChannelFundConfig.class);
		for (ChannelFundConfig channelFundConfig : channelFundConfigList) {
			if (channelFundConfig.getFeeType() == null) {
				channelFundConfig.setFeeType(1);
			}
			if (channelFundConfig.getRateType() == null) {
				channelFundConfig.setRateType(1);
			}
			if (StringUtils.isEmpty(channelFundConfig.getRate())) {
				channelFundConfig.setRate("0");
			}
		}
		return JSON.toJSONString(channelFundConfigList);
	}
	
	/**
	 * 创建渠道资方配置审批记录
	 * @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);
	}
	
	@Override
	public FundModuleChannelFundConfig auditPassConfig(Long preId, Long auditId) {
		FundModuleChannelFundConfig config = updateEnable(auditId, true);
		if (preId != null) {
			updateEnable(preId, false);
		}
		return config;
	}
	
	/**
	 * 更改配置enable状态
	 * @param id
	 * @param enable
	 */
	private FundModuleChannelFundConfig updateEnable(Long id, Boolean enable) {
		FundModuleChannelFundConfig config = fundModuleChannelFundConfigRepository.findById(id);
		config.setEnable(enable);
		return fundModuleChannelFundConfigRepository.save(config);
	}

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

	/**
	 * 渠道资方配置按条件类型分组
	 * @param fundModuleChannelFundConfig
	 * @return
	 */
	private FundModuleChannelFundConfig groupByLimit(FundModuleChannelFundConfig fundModuleChannelFundConfig) {
		List<ChannelFundConfig> fundConfigList = JSONArray.parseArray(fundModuleChannelFundConfig.getFunds(), ChannelFundConfig.class);
		// 条件类型分组, key:条件类型
		Map<String, List<ChannelFundConfig>> channelFundConfigMap = new LinkedHashMap<>();
		for (ChannelFundConfig channelFundConfig : fundConfigList) {
			List<ChannelFundConfig.Limit> limits = channelFundConfig.getLimits();
			// 先把每个资方下的条件排序，保证多个条件下的条件顺序相同
			Collections.sort(limits, Comparator.comparingInt(l -> l.getLimit().hashCode()));

			StringBuilder key = new StringBuilder();
			limits.forEach(limit -> key.append(limit.getLimit()));
			if (channelFundConfigMap.get(key.toString()) == null) {
				channelFundConfigMap.put(key.toString(), new ArrayList<>());
			}
			channelFundConfigMap.get(key.toString()).add(channelFundConfig);
		}

		// 分完组后，开始对组内优先级进行排序
		channelFundConfigMap.entrySet().stream().forEach(entry -> {
			Collections.sort(entry.getValue(), Comparator.comparingInt(config -> config.getPriority()));
		});

		// 重新组装
		List<ChannelFundConfig> sortedList = new ArrayList<>();
		channelFundConfigMap.entrySet().forEach(e -> {
			sortedList.addAll(e.getValue());
		});
		fundModuleChannelFundConfig.setFunds(JSON.toJSONString(sortedList));
		return fundModuleChannelFundConfig;
	}
}
