package com.bocloud.ims.service.scheduler.scheduler;

import com.alibaba.fastjson.JSONObject;
import com.bocloud.cmp.driver.common.BillingDriver;
import com.bocloud.cmp.entity.enums.CloudProvider;
import com.bocloud.cmp.entity.enums.ResourceCategory;
import com.bocloud.cmp.model.PublicCloudBillModel;
import com.bocloud.common.model.BocloudResult;
import com.bocloud.common.model.RequestContext;
import com.bocloud.common.utils.ListTool;
import com.bocloud.ims.entity.common.Tenant;
import com.bocloud.ims.entity.resource.*;
import com.bocloud.ims.repository.resource.*;
import com.bocloud.ims.service.internal.SmsTenantService;
import com.bocloud.service.core.CurrentService;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 公有云账单
 *
 * @author ymh
 * @时间 2019年12月18日
 */
@Component
public class PublicCloudBillingScheduler implements InitializingBean {

	private static Logger logger = LoggerFactory.getLogger(PublicCloudBillingScheduler.class);
	@Autowired
	private CurrentService service;
	@Autowired
	private CloudVendorRepository cloudVendorRepository;
	@Autowired
	private BillingDriver billingDriver;
	@Autowired
	private CloudBillRepository cloudBillRepository;
	@Autowired
	private SmsTenantService smsTenantService;
	@Autowired
	private ThreadPoolTaskExecutor taskExecutor;
	@Autowired
	private CloudServerRepository serverRepository;
	@Autowired
	private VolumeRepository volumeRepository;
	@Autowired
	private ObjectRelationRepository relationRepository;

	@Scheduled(cron = "${public.cloud.bill.schedule:0 0 1 * * ?}")
	protected void sync() {
		if (!service.isLeader()) {
			return;
		}
		taskExecutor.submit(() -> {
			logger.info("start sync public cloud bill scheduler ... ");
			List<String> types = Arrays.asList(new String[] { "ALIYUN", "HUAWEI" });
			List<CloudVendor> cloudVendors = cloudVendorRepository.listByTypes(types, null);
			syncCloudBills(cloudVendors);
			logger.info("end sync public cloud bill scheduler ... ");
		});
	}

	private void syncCloudBills(List<CloudVendor> cloudVendors) {
		try {
			if (ListTool.isEmpty(cloudVendors)) {
				return;
			}
			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM");
			String month = format.format(new Date());
			for (CloudVendor vendor : cloudVendors) {
				// 平台配置信息
				JSONObject authentication = JSONObject.parseObject(vendor.getAuthentication());
				String regions = authentication.getString("regions");
				List<String> currentRegions = JSONObject.parseArray(regions, String.class);
				if (ListTool.isEmpty(currentRegions)) {
					logger.info(vendor.getName() + " 平台获取区域信息异常，账单同步失败！");
					continue;
				}
				BocloudResult bocloudResult = billingDriver.billDetailList(vendor.getUuid(), month,
						currentRegions.get(0));
				@SuppressWarnings("unchecked")
				List<PublicCloudBillModel> billModels = (List<PublicCloudBillModel>) bocloudResult.getData();
				if (ListTool.isEmpty(billModels)) {
					continue;
				}
				syncBills(billModels, vendor, month);
				// 计算租户二次计价金额
				if (CloudProvider.valueOf(vendor.getType()) == CloudProvider.AZURE) {
					syncTenantBill();
				}
			}
		} catch (Exception e) {
			logger.error("同步公有云账单异常：" + e.getMessage());
		}
	}

	private void syncBills(List<PublicCloudBillModel> billModels, CloudVendor vendor, String month) throws Exception {
		if (ListTool.isEmpty(billModels)) {
			cloudBillRepository.deleteByVendor(vendor.getId(), null);
			return;
		}
		List<CloudBill> cloudBills = cloudBillRepository.listByVendor(vendor.getId(), null);
		Map<String, CloudBill> cloudBillMap = Maps.newHashMap();
		for (CloudBill cloudBill : cloudBills) {
			cloudBillMap.put(cloudBill.getOrderSn(), cloudBill);
		}
		for (PublicCloudBillModel model : billModels) {
			if (cloudBillMap.containsKey(model.getOrderSn())) {
				cloudBillMap.remove(model.getOrderSn());
			} else {
				CloudBill cloudBill = JSONObject.parseObject(JSONObject.toJSONString(model), CloudBill.class);
				cloudBill.setVendorId(vendor.getId());
				cloudBill.setMonth(month);
				cloudBill.setVendorType(vendor.getType());
				cloudBill.setOrderSn(cloudBill.getOrderSn());
				cloudBill.setStatus("paid"); // 已支付
				cloudBill.setChargeMode("PostCharge"); // 后付费
				cloudBill.setUnitPrice(model.getUnitPrice());
				cloudBillRepository.save(cloudBill);
			}
		}
		for (Map.Entry<String, CloudBill> entry : cloudBillMap.entrySet()) {
			cloudBillRepository.delete(entry.getValue().getId());
		}

	}

	/**
	 * 同步租户账单，进行二次计价
	 *
	 * @throws Exception
	 */
	private void syncTenantBill() throws Exception {
		List<String> types = Arrays.asList(new String[] { CloudProvider.AZURE.name() });
		List<CloudVendor> cloudVendors = cloudVendorRepository.listByTypes(types, null);
		List<CloudBill> bills = new ArrayList<>();
		for (CloudVendor vendor : cloudVendors) {
			List<CloudBill> cloudBills = cloudBillRepository.listByVendor(vendor.getId(), null);
			if (!ListTool.isEmpty(cloudBills)) {
				bills.addAll(cloudBills);
			}
		}
		BocloudResult<List<Tenant>> result = smsTenantService.listAll(new ArrayList<>());
		List<CloudServer> servers = serverRepository.list(1, Integer.MAX_VALUE, new ArrayList<>(), new HashMap<>());
		Map<String, CloudServer> serverMap = servers.stream().filter(cloudServer -> null != cloudServer.getInstanceId())
				.collect(Collectors.toMap(CloudServer::getInstanceId, Function.identity()));
		List<Volume> volumes = volumeRepository.list(1, Integer.MAX_VALUE, new ArrayList<>(), new HashMap<>());
		Map<String, Volume> volumeMap = volumes.stream().filter(volume -> null != volume.getVolumeUuid())
				.collect(Collectors.toMap(Volume::getVolumeUuid, Function.identity()));
		if (result.isSuccess()) {
			List<Tenant> tenants = result.getData();
			if (!ListTool.isEmpty(tenants)) {
				Map<Long, BigDecimal> discountMap = tenants.stream().filter(tenant -> null != tenant.getDiscount())
						.collect(Collectors.toMap(Tenant::getId, Tenant::getDiscount));
				List<CloudBill> updateBills = new ArrayList<>();
				for (CloudBill bill : bills) {
					String resourceId = bill.getResourceId();
					if (StringUtils.isEmpty(resourceId)) {
						continue;
					}
					CloudServer server = serverMap.get(resourceId);
					if (null != server) {
						bill.setTenantId(server.getTenantId());
						bill.setProjectId(server.getProjectId());
						ObjectRelation relation = relationRepository.get(server.getId(),
								ResourceCategory.Computer.name());
						if (null != relation) {
							if (RequestContext.Catalog.User.name().equalsIgnoreCase(relation.getCatalog())) {
								bill.setUserId(relation.getTarget());
							}
						}
						// 计算租户价格
						BigDecimal discount = discountMap.get(bill.getTenantId());
						if (null != discount) {
							bill.setTenantMoney(bill.getTotalCost().multiply(discount));
						}
						updateBills.add(bill);
					} else {
						Volume volume = volumeMap.get(resourceId);
						if (null != volume) {
							bill.setTenantId(volume.getTenantId());
							bill.setProjectId(volume.getProjectId());
							ObjectRelation relation = relationRepository.get(volume.getId(),
									ResourceCategory.Storage.name());
							if (null != relation) {
								if (RequestContext.Catalog.User.name().equalsIgnoreCase(relation.getCatalog())) {
									bill.setUserId(relation.getTarget());
								}
							}
							// 计算租户价格
							BigDecimal discount = discountMap.get(bill.getTenantId());
							if (null != discount) {
								bill.setTenantMoney(bill.getTotalCost().multiply(discount));
							}
							updateBills.add(bill);
						}
					}
				}
				if (!ListTool.isEmpty(updateBills)) {
					cloudBillRepository.batchUpdate(updateBills, 1000);
				}
			}
		}

	}

	@Override
	public void afterPropertiesSet() throws Exception {
//		sync();
	}

}
