package com.bizzan.bitrade.engine;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bizzan.bitrade.constant.NotifyType;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.List;
import com.bizzan.bitrade.entity.CloseType;
import com.bizzan.bitrade.entity.ContractCoin;
import com.bizzan.bitrade.entity.ContractMoneyType;
import com.bizzan.bitrade.entity.ContractOrderDirection;
import com.bizzan.bitrade.entity.ContractOrderEntrust;
import com.bizzan.bitrade.entity.ContractOrderType;
import com.bizzan.bitrade.entity.ContractPositionType;
import com.bizzan.bitrade.entity.FollowAccount;
import com.bizzan.bitrade.entity.MemberContractPosition;
import com.bizzan.bitrade.entity.MemberContractWallet;
import com.bizzan.bitrade.service.ContractCoinService;
import com.bizzan.bitrade.service.ContractOrderEntrustService;
import com.bizzan.bitrade.service.FollowWalletService;
import com.bizzan.bitrade.service.MemberContractPositionService;
import com.bizzan.bitrade.service.MemberService;
import com.bizzan.bitrade.service.RedisLock;
import com.bizzan.bitrade.service.SysConfigService;
import com.bizzan.bitrade.service.UserPurseService;
import com.bizzan.bitrade.util.MessageResult;
import com.bizzan.bitrade.util.RedisManager;
import com.bizzan.bitrade.vo.TradingAccount;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class FollowAccountMonitor {

	@Autowired
	private FollowWalletService followWalletService;
	@Autowired
	private MemberContractPositionService memberContractPositionService;
	@Autowired
	private SysConfigService sysConfigService;
	@Autowired
	private ContractCoinService contractCoinService;
	@Autowired
    private ContractCoinMatchFactory contractCoinMatchFactory;
	@Autowired
	private MemberService memberService;
	@Autowired
	private RedisLock redisLock;
	@Autowired
	private RedisManager redisManager;
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

	private static final String REDIS_PRICE_KEY = "NEW_COIN_PRICE:";

	private ConcurrentHashMap<Long, FollowAccount> accountMap;
	/** ContractCoin 本地缓存，由 checkBlast() 每 1.5s 刷新，避免 genVolumeForOrder 重复查库 */
	private final ConcurrentHashMap<Long, ContractCoin> coinCache = new ConcurrentHashMap<>();
	
	private Map<String, BigDecimal> priceMapAtPresent = new HashMap<String, BigDecimal>();
	
	public void initAccounts() {
		accountMap = followWalletService.findAllWithPositions();
	}
	
	public Map<String, BigDecimal> getPriceMapAtPresent() {
		return priceMapAtPresent;
	}
	
	public void updateAccount(Long id) {
		FollowAccount account = followWalletService.findOneVO(id);
		synchronized(accountMap) {
			if(account.getCurrentPositionList()==null || account.getCurrentPositionList().size()==0)
				accountMap.remove(account.getId());
			else
				accountMap.put(id, account);
		}
		followWalletService.deleteVOFromRedis(id);
	}
	
	public FollowAccount get(Long id) {
		FollowAccount account = accountMap.get(id);
		if (account!=null)
			return account;
		else {
			account = followWalletService.findOneVOFromRedis(id);
			account.setCurrentPriceMap(priceMapAtPresent);
			return account;
		}
	}

	/** 从本地缓存获取 ContractCoin，缺失时降级查库（coinCache 由 checkBlast 每 1.5s 刷新） */
	public ContractCoin getCoin(Long contractId) {
		ContractCoin coin = coinCache.get(contractId);
		return coin != null ? coin : contractCoinService.findOne(contractId);
	}

	/**
	 * 批量预加载账户到 accountMap，用于 batchFollowOpen 开线程前调用。
	 * 对不在 accountMap 的成员做一次批量 SELECT，避免每个线程各自查库（N次 → 1次）。
	 */
	public void preloadAccounts(List<Long> memberIds) {
		List<Long> missing = new ArrayList<>();
		for (Long id : memberIds) {
			if (id != null && !accountMap.containsKey(id)) {
				missing.add(id);
			}
		}
		if (missing.isEmpty()) return;
		log.info("【预加载账户】accountMap 缺失 {} 个成员，批量查库", missing.size());
		List<com.bizzan.bitrade.entity.FollowWallet> wallets = followWalletService.findByIds(missing);
		for (com.bizzan.bitrade.entity.FollowWallet wallet : wallets) {
			FollowAccount vo = new FollowAccount();
			org.springframework.beans.BeanUtils.copyProperties(wallet, vo);
			vo.setCurrentPriceMap(priceMapAtPresent);
			accountMap.put(vo.getId(), vo);
		}
		log.info("【预加载账户】完成，加载 {} 个账户到 accountMap", wallets.size());
	}
	
	@Scheduled(fixedDelay = 1500)
	public void checkBlast() {
		List<ContractCoin> coinList = contractCoinService.findAllEnabled();
		for (ContractCoin coin : coinList) {
			coinCache.put(coin.getId(), coin);
			Object priceObj = redisTemplate.opsForHash().get(REDIS_PRICE_KEY, coin.getId().toString());
			if (priceObj != null) {
				try { priceMapAtPresent.put(coin.getSymbol(), new BigDecimal(priceObj.toString())); } catch (Exception ignored) {}
			}
		}
		if (contractCoinMatchFactory.getMatchMap().size() != priceMapAtPresent.size()) {
			log.info("waiting for the price to be ready, matchMap={}, priceMap={}", contractCoinMatchFactory.getMatchMap().size(), priceMapAtPresent.size());
			return;
		}
		//log.info("currentPriceMap={}", currentPriceMap);
		//log.info("priceMapAtPresent={}", priceMapAtPresent);
		int marginRate = sysConfigService.get().getContractMarginRate();
		
		synchronized(accountMap) {
			for(FollowAccount account:accountMap.values()) {
				account.setCurrentPriceMap(priceMapAtPresent);
				//log.info("checking blast memberID={}, currentCapital={}", tradingAccount.getId(), tradingAccount.getCurrentCapital());
				if(account.getFrozenBalance().compareTo(BigDecimal.ZERO)==0) {
					continue;
				}
				BigDecimal currentMargin = account.getCurrentMargin();
				if(currentMargin.compareTo(BigDecimal.ZERO)==0) {
					//log.info("currentMargin is zero  memberID={}", tradingAccount.getId());
					continue;
				}
				BigDecimal profitOrLoss = account.getCurrentCapital().subtract(account.getStaticBalance());
				if(profitOrLoss.compareTo(BigDecimal.ZERO)>0 && account.getProfitPercent()!=null) {
					BigDecimal profitPercent = profitOrLoss.multiply(BigDecimal.valueOf(100)).divide(account.getStaticBalance(), 4, BigDecimal.ROUND_DOWN);
					if(profitPercent.compareTo(account.getProfitPercent())>0) {
						forceClose(account, CloseType.FOLLOW_ACCOUNT_PROFIT);
						continue;
					}
				}
				if(profitOrLoss.compareTo(BigDecimal.ZERO)<0 && account.getLossPercent()!=null) {
					BigDecimal lossPercent = BigDecimal.ZERO.subtract(profitOrLoss.multiply(BigDecimal.valueOf(100)).divide(account.getStaticBalance(), 4, BigDecimal.ROUND_DOWN));
					if(lossPercent.compareTo(account.getLossPercent())>0) {
						forceClose(account, CloseType.FOLLOW_ACCOUNT_LOSS);
						continue;
					}
				}
				double currentMarginRate = account.getCurrentCapital().divide(currentMargin, 4, BigDecimal.ROUND_DOWN).doubleValue()*100;
				if(currentMarginRate<100) {
					sendBlastAlarmEmail(account.getId(), currentMarginRate);
				}
				if(currentMarginRate<marginRate) {
					accountMap.remove(account.getId());
					kafkaTemplate.send("follow-account-blast", JSON.toJSONString(account));
				}
			}
		}
	}
	
	private void forceClose(FollowAccount account, CloseType closeType) {
		Set<MemberContractPosition> currentPositionList = account.getCurrentPositionList();
		//int positionIndex = 0;
		for(MemberContractPosition position: currentPositionList) {
			position.setCloseType(closeType);
			
			//contractCoinMatchFactory.getContractCoinMatch(position.getCoin().getSymbol()).dealAllCloseOrder(position);
			kafkaTemplate.send("swap-order-close-all", JSON.toJSONString(position));
			
		}
		//BigDecimal blastedLoss = contractOrderEntrustService.sumBlastedLossByPositionIds(positionIds);
		//userPurseService.zeroBalance(refreshedTradingAccount.getId());
		accountMap.remove(account.getId());
	}
	
	private void sendMessagePush(Long memberId, String type) {
		JSONObject jsonObject = new JSONObject();
        jsonObject.put("memberId", memberId);
        jsonObject.put("type", type);
        kafkaTemplate.send("follow-message-push", JSON.toJSONString(jsonObject));
	}
	
	@Scheduled(cron = "0 0 0,8,16 * * ?")
	public void collectOvernightFee() {
		log.info("start collecting follow overnight fee");
		Map<Integer, BigDecimal> fundingRateMap = redisManager.getFundingRateMap();
		//int fundingRebate = sysConfigService.get().getFundingRebate();
		//int overnightRate = sysConfigService.get().getOvernightRate();
		synchronized(accountMap) {
			for(FollowAccount account:accountMap.values()) {
				account.setCurrentPriceMap(priceMapAtPresent);
				//if(fundingRebate == 0) {
					Map<ContractCoin, BigDecimal> marketValueMap = account.findMarginMap();
					for(ContractCoin coin : marketValueMap.keySet()) {
						if(!fundingRateMap.containsKey(coin.getId().intValue()))
							continue;
						BigDecimal fundingRate = fundingRateMap.get(coin.getId().intValue());
						BigDecimal overnightFee = marketValueMap.get(coin).multiply(fundingRate);
						//BigDecimal overnightFee = marketValueMap.get(coin).multiply(BigDecimal.valueOf(overnightRate/100000));
						followWalletService.clearContractOvernightFee(account.getId(), coin, overnightFee, BigDecimal.ZERO);
						
					}
				//} else {
					/*Map<ContractCoin, BigDecimal[]> specialMarketValueMap = account.findSpecialMarginMap();
					for(ContractCoin coin : specialMarketValueMap.keySet()) {
						if(!fundingRateMap.containsKey(coin.getId().intValue()))
							continue;
						BigDecimal fundingRate = fundingRateMap.get(coin.getId().intValue());
						BigDecimal[] marginWithSpecial = specialMarketValueMap.get(coin);
						BigDecimal overnightFee = marginWithSpecial[0].multiply(fundingRate).add(marginWithSpecial[1].multiply(fundingRate));
						BigDecimal rebateFee = marginWithSpecial[0].multiply(fundingRate).multiply(BigDecimal.valueOf(fundingRebate/100.0));
						followWalletService.clearContractOvernightFee(account.getId(), coin, overnightFee, rebateFee);
						
					}*/
				//}
				updateAccount(account.getId());
			}
		}
		log.info("end collecting follow overnight fee");
	}
	
	private void sendBlastAlarmEmail(Long memberId, double marginRate) {
		net.sf.json.JSONObject jsonObject = new net.sf.json.JSONObject();
		jsonObject.put("marginRate", String.format("%.2f", marginRate));
        
        if(redisManager.getFollowBlastAlarm(memberId)) {
        	memberService.notifyByEmail(memberId, NotifyType.FOLLOW_BLAST_ALARM, jsonObject);
        }
	}
	
	/**
	 * 计算跟单开仓手数并设置 order 字段。
	 * @param freeze true=同时冻结保证金（引擎路径使用）；false=只计算不冻结（批量直接开仓路径使用，冻结在 batchDirectOpen 事务内完成）
	 */
	public boolean genVolumeForOrder(ContractOrderEntrust order, long followMemberId, boolean freeze) {
		if(order.getType()!=ContractOrderType.MARKET_PRICE && order.getType()!=ContractOrderType.LIMIT_PRICE)
			return false;
		try {
			// 使用内存缓存，避免每条订单重复查库（priceMapAtPresent 由 checkBlast 每 1.5s 刷新）
	    	FollowAccount account = this.get(order.getMemberId());
	    	ContractCoin contractCoin = coinCache.containsKey(order.getContractId())
	    			? coinCache.get(order.getContractId())
	    			: contractCoinService.findOne(order.getContractId());
	    	BigDecimal leverage = BigDecimal.valueOf(order.getLeverage());
	    	//memberContractPositionService.adjustMarginByCurrentPrice(order.getMemberId(), priceMap);
	    	BigDecimal currentContractMargin = account.sumCurrentMarginByContractId(order.getContractId());
	    	BigDecimal currentContractFollowMargin = account.sumCurrentMarginByContractIdAndFollowMemberId(order.getContractId(), followMemberId);

	    	BigDecimal fee;
	    	if (contractCoin.getFollowFee()!=null)
	    		fee = contractCoin.getFollowFee();
	    	else
	    		fee = contractCoin.getOpenFee();
	    	
	    	//contractCoinMatchFactory.getContractCoinMatch(contractCoin.getSymbol()).genTradedPriceForOrder(order, limitPrice);
	    	BigDecimal tradedPrice = order.getTradedPrice();
	    	if(tradedPrice.compareTo(BigDecimal.ZERO)<=0)
	    		return false;
	    	
	    	
        	BigDecimal availableBalanceForOpen = account.getAvailableBalanceForOpen();
            BigDecimal specialCap = BigDecimal.valueOf(sysConfigService.get().getFollowSpecialAmount());
        	// 无损跟单：计算基数不超过 followSpecialAmount（如 3000 USDT）
        	if (order.getIsSpecial() != null && order.getIsSpecial() == 1) {
        		if (availableBalanceForOpen.compareTo(specialCap) > 0)
        			availableBalanceForOpen = specialCap;
        	}

            BigDecimal maxBalanceForOpen = contractCoin.getMaxTotalShare().subtract(currentContractMargin).multiply(BigDecimal.ONE.add(leverage.multiply(contractCoin.getOpenFee())));
        	if (order.getMaxFollowAmount() != null) {
                log.info("测试getMaxFollowAmount：{}",order.getMaxFollowAmount());
                log.info("currentContractFollowMargin：{}",currentContractFollowMargin);
        		BigDecimal maxFollowAmount = order.getMaxFollowAmount().subtract(currentContractFollowMargin).multiply(BigDecimal.ONE.add(leverage.multiply(contractCoin.getOpenFee())));
                log.info("计算后的maxFollowAmount：{}",maxFollowAmount);
        		if (maxFollowAmount.compareTo(maxBalanceForOpen) < 0)
        			maxBalanceForOpen = maxFollowAmount;
                log.info("计算后的maxBalanceForOpen：{}",maxBalanceForOpen);
        	}
        	
        	if(availableBalanceForOpen.compareTo(maxBalanceForOpen)>0)
        		availableBalanceForOpen = maxBalanceForOpen;
        	BigDecimal volume = availableBalanceForOpen
        			.multiply(order.getPercentageVolume())
        			.divide(tradedPrice.multiply(fee.add(BigDecimal.ONE.divide(leverage, 8, BigDecimal.ROUND_UP))), order.getDecimalScale(), BigDecimal.ROUND_DOWN);
	        	

	    	if(volume.compareTo(BigDecimal.ZERO)<=0) {
	    		log.warn("genVolumeForOrder 失败：计算手数为0, memberId={}, availableBalanceForOpen={},  tradedPrice={}, leverage={}, percentageVolume={}",
	    				order.getMemberId(), availableBalanceForOpen,  tradedPrice, leverage, order.getPercentageVolume());
	    		return false;
	    	}


	        order.setVolume(volume);
	        BigDecimal tradedMoney;
	        tradedMoney = volume.multiply(tradedPrice);
	        BigDecimal principalAmount = tradedMoney.divide(leverage, 8, BigDecimal.ROUND_DOWN);
	        BigDecimal openFee = tradedMoney.multiply(fee).setScale(8, BigDecimal.ROUND_DOWN);
	        BigDecimal required = principalAmount.add(openFee);

	    	if(availableBalanceForOpen.compareTo(required)<=0) {
	    		log.warn("genVolumeForOrder 失败：可用余额不足, memberId={}, 需要={}, 实际可用={}, principalAmount={}, openFee={}",
	    				order.getMemberId(), required, availableBalanceForOpen, principalAmount, openFee);
	        	return false;
	        }
	        
	        order.setPrincipalAmount(principalAmount);
	        order.setOpenFee(openFee);
	        order.setIsPercentage(0);
	        if (!freeze) return true; // 批量路径：只计算，冻结由 batchDirectOpen 事务内完成
	        return followWalletService.freezeMarginAndFee(order.getMemberId(), contractCoin, principalAmount.add(openFee));
		} catch (Exception e) {
			log.info(e.getMessage(), e);
			return false;
		}
    }

	/** 向后兼容重载：引擎路径调用，freeze=true（计算+冻结）。 */
	public boolean genVolumeForOrder(ContractOrderEntrust order, long followMemberId) {
		return genVolumeForOrder(order, followMemberId, true);
	}
}
