package com.ecep.contract.ds.project;

import static com.ecep.contract.SpringApp.getBean;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.hibernate.Hibernate;

import com.ecep.contract.MessageHolder;
import com.ecep.contract.ds.contract.service.ContractItemService;
import com.ecep.contract.ds.contract.service.ContractService;
import com.ecep.contract.ds.other.service.InventoryService;
import com.ecep.contract.ds.project.service.ProjectCostItemService;
import com.ecep.contract.ds.project.service.ProjectCostService;
import com.ecep.contract.model.Contract;
import com.ecep.contract.model.ContractItem;
import com.ecep.contract.model.Employee;
import com.ecep.contract.model.Inventory;
import com.ecep.contract.model.Project;
import com.ecep.contract.model.ProjectCost;
import com.ecep.contract.model.ProjectCostItem;
import com.ecep.contract.ui.Tasker;
import com.ecep.contract.util.NumberUtils;
import com.ecep.contract.util.TaxRateUtils;

import lombok.Setter;

public class ProjectCostImportItemsFromContractsTasker extends Tasker<Void> {
    @Setter
    private ProjectCostService costService;
    @Setter
    private ContractService contractService;
    @Setter
    private ProjectCostItemService itemService;
    @Setter
    private ContractItemService contractItemService;
    @Setter
    private Supplier<Employee> currentUser;

    @Setter
    private InventoryService inventoryService;
    @Setter
    ProjectCost cost;

    @Override
    protected Void execute(MessageHolder holder) throws Exception {
        importFromContracts(cost, holder);
        return null;
    }

    public void importFromContracts(ProjectCost projectCost, MessageHolder holder) {
        Project project = projectCost.getProject();
        List<ProjectCostItem> projectCostItems = getItemService().findByCost(projectCost);
        List<Contract> salesContracts = getContractService().findAllSalesByProject(project);
        holder.debug("检索到 " + salesContracts.size() + " 个销售合同, 导入合同数据...");

        //
        List<ContractItem> contractItems = new ArrayList<>();
        // 根据存货匹配，可多个相同的存货
        Map<Inventory, List<ContractItem>> map = new HashMap<>();
        for (Contract salesContract : salesContracts) {
            // 收集销售合同的条目
            List<ContractItem> list = getContractItemService().findAllByContract(salesContract);
            contractItems.addAll(list);
            holder.debug("销售合同：" + salesContract.getCode() + " " + salesContract.getName() + " 收集条目：" + list.size());

            // 销售合同对应的采购合同的条目
            List<Contract> purchaseContracts = getContractService().findAllByParent(salesContract);
            holder.debug("检索到对应采购合同：" + purchaseContracts.size());
            for (Contract contract : purchaseContracts) {
                List<ContractItem> items = getContractItemService().findAllByContract(contract);
                holder.debug("采购合同：" + contract.getCode() + " " + contract.getName() + " 收集条目：" + items.size());
                for (ContractItem item : items) {
                    // 根据存货汇总
                    map.computeIfAbsent(item.getInventory(), k -> new ArrayList<>()).add(item);
                }
            }
        }

        compositeIn(contractItems, projectCostItems, holder);

        compositeOut(map, projectCostItems, holder);

        up(projectCost, projectCostItems);

        getCostService().save(projectCost);
    }

    /**
     * 计算统计数
     *
     * @param projectCost
     * @param items
     */
    public void up(ProjectCost projectCost, List<ProjectCostItem> items) {
        // 含税进项总金额
        double inTaxAmount = 0;
        // 不含税进项总金额
        double inExclusiveTaxAmount = 0;
        // 含税出项总金额
        double outTaxAmount = 0;
        // 不含税出项总金额
        double outExclusiveTaxAmount = 0;

        for (ProjectCostItem row : items) {
            inTaxAmount += TaxRateUtils.toInclusiveTax(row.getInExclusiveTaxPrice(), row.getInTaxRate())
                    * row.getInQuantity();
            inExclusiveTaxAmount += row.getInExclusiveTaxPrice() * row.getInQuantity();

            outTaxAmount += TaxRateUtils.toExclusiveTax(row.getOutExclusiveTaxPrice(), row.getOutTaxRate())
                    * row.getOutQuantity();
            outExclusiveTaxAmount += row.getOutExclusiveTaxPrice() * row.getOutQuantity();
        }

        projectCost.setInTaxAmount(inTaxAmount);
        projectCost.setInExclusiveTaxAmount(inExclusiveTaxAmount);
        projectCost.setOutTaxAmount(outTaxAmount);
        projectCost.setOutExclusiveTaxAmount(outExclusiveTaxAmount);

        projectCost.setStampTaxFee((float) (inTaxAmount / 100 * projectCost.getStampTax()));
        projectCost.setTaxAndSurchargesFee(
                (float) (((inTaxAmount - inExclusiveTaxAmount) - (outTaxAmount - outExclusiveTaxAmount))
                        * projectCost.getTaxAndSurcharges() / 100));
        projectCost.setGrossProfitMargin(
                (float) (inExclusiveTaxAmount - outExclusiveTaxAmount - projectCost.getStampTaxFee()
                        - projectCost.getTaxAndSurchargesFee()) * projectCost.getTaxAndSurcharges() / 100);
    }

    String toKey(ContractItem item) {
        return item.getTitle() + "_" + item.getSpecification();
    }

    String toKey(ProjectCostItem item) {
        return item.getTitle() + "_" + item.getSpecification();
    }

    private void compositeOut(Map<Inventory, List<ContractItem>> map, List<ProjectCostItem> results,
            MessageHolder holder) {
        // 根据存货匹配，可对多个相同的存货进行合并
        for (Map.Entry<Inventory, List<ContractItem>> entry : map.entrySet()) {
            Inventory inventory = Hibernate.isInitialized(entry.getKey()) ? entry.getKey()
                    : getInventoryService().findById(entry.getKey().getId());
            if (inventory == null) {
                // 存货编号没有，则使用 项目名称+规格 来判断是否有数据
                entry.getValue().stream().filter(item -> item.getInventory() == null)
                        .collect(Collectors.groupingBy(this::toKey)).forEach((key, value) -> {
                            List<ProjectCostItem> list = results.stream()
                                    .filter(v -> v.getInventory() == null && key.equals(toKey(v))).toList();
                            compositeOut(null, value, list, results);
                        });
            } else {
                List<ProjectCostItem> list = results.stream().filter(v -> Objects.equals(v.getInventory(), inventory))
                        .toList();
                compositeOut(inventory, entry.getValue(), list, results);
            }
        }
    }

    private void compositeIn(List<ContractItem> contractItems, List<ProjectCostItem> results, MessageHolder holder) {
        // 存货编号没有，则使用 项目名称+规格 来判断是否有数据
        contractItems.stream().filter(item -> item.getInventory() == null).collect(Collectors.groupingBy(this::toKey))
                .forEach((key, value) -> {
                    List<ProjectCostItem> list = results.stream()
                            .filter(v -> v.getInventory() == null && key.equals(toKey(v))).toList();
                    compositeIn(null, value, list, results, holder);
                });

        // 根据存货编号来判断是否有数据
        contractItems.stream().filter(item -> item.getInventory() != null)
                .collect(Collectors.groupingBy(ContractItem::getInventory)).forEach((inventory, value) -> {
                    List<ProjectCostItem> list = results.stream()
                            .filter(v -> Objects.equals(v.getInventory(), inventory)).toList();
                    compositeIn(inventory, value, list, results, holder);
                });
    }

    private void compositeIn(
            Inventory inventory,
            List<ContractItem> contractItems, List<ProjectCostItem> costItems,
            List<ProjectCostItem> results, MessageHolder holder) {
        // contractItems 对应的 costItems
        // 同步处理 contractItems 中的多个 ContractItem 和 costItems 中的多个 ProjectCostItem
        for (int i = 0; i < Math.max(contractItems.size(), costItems.size()); i++) {
            ContractItem contractItem = i < contractItems.size() ? contractItems.get(i) : null;
            ProjectCostItem projectCostItem = i < costItems.size() ? costItems.get(i) : null;

            if (contractItem == null) {
                if (projectCostItem != null) {
                    if (results.remove(projectCostItem)) {
                        getItemService().delete(projectCostItem);
                        holder.warn("删除成本项：" + projectCostItem.toPrettyString());
                    }
                }
                continue;
            }

            if (projectCostItem == null) {
                // 添加新的项目
                projectCostItem = new ProjectCostItem();
                projectCostItem.setCost(cost);
                projectCostItem.setInventory(contractItem.getInventory());
                projectCostItem.setTitle(contractItem.getTitle());
                projectCostItem.setSpecification(contractItem.getSpecification());
                projectCostItem.setUnit(contractItem.getUnit());
                projectCostItem.setCreator(currentUser.get());
                projectCostItem.setCreateDate(LocalDateTime.now());
                projectCostItem = save(projectCostItem);
                results.add(projectCostItem);
                holder.info("新增成本项：" + projectCostItem.toPrettyString());
            } else {
                projectCostItem.setUpdater(currentUser.get());
                projectCostItem.setUpdateDate(LocalDateTime.now());
            }

            // 更新已存在的项目
            if (!Objects.equals(projectCostItem.getInventory(), contractItem.getInventory())) {
                projectCostItem.setInventory(contractItem.getInventory());
                holder.info("更新进项成本的库存为：" + contractItem.getInventory().toPrettyString());
            }
            if (!Objects.equals(projectCostItem.getTitle(), contractItem.getTitle())) {
                projectCostItem.setTitle(contractItem.getTitle());
                holder.info("更新进项成本的标题为：" + contractItem.getTitle());
            }
            if (!Objects.equals(projectCostItem.getSpecification(), contractItem.getSpecification())) {
                projectCostItem.setSpecification(contractItem.getSpecification());
                holder.info("更新进项成本的规格为：" + contractItem.getSpecification());
            }
            if (!Objects.equals(projectCostItem.getUnit(), contractItem.getUnit())) {
                projectCostItem.setUnit(contractItem.getUnit());
                holder.info("更新进项成本的计量单位为：" + contractItem.getUnit());
            }
            transportInTaxRate(projectCostItem, contractItem, holder);
            if (!NumberUtils.equals(projectCostItem.getInQuantity(), contractItem.getQuantity())) {
                projectCostItem.setInQuantity(contractItem.getQuantity());
                holder.info("更新进项成本的数量为：" + contractItem.getQuantity());
            }
            save(projectCostItem);
        }
    }

    /**
     * 处理不同时期的税率，将2019-04-01后的17%的税率改为11%
     */
    private void transportInTaxRate(ProjectCostItem costItem, ContractItem contractItem, MessageHolder holder) {
        if (contractItem.getTaxRate() >= TaxRateUtils.TAX_RATE_GENERAL_OLDEST) {
            // 2019-04-01 开始税率调整为11%
            if (TaxRateUtils.isAfterTaxRateChangeTo13(cost.getApplyTime())) {
                // 含税价
                double v = TaxRateUtils.toInclusiveTax(contractItem.getExclusiveTaxPrice(), contractItem.getTaxRate());
                costItem.setInTaxRate(TaxRateUtils.TAX_RATE_GENERAL);
                double exclusiveTaxPrice = TaxRateUtils.toExclusiveTax(v, costItem.getInTaxRate());
                if (!NumberUtils.equals(costItem.getInExclusiveTaxPrice(), exclusiveTaxPrice)) {
                    costItem.setInExclusiveTaxPrice(exclusiveTaxPrice);
                    holder.warn("成本进项税自动调整 " + contractItem.getTaxRate() + " -> " + costItem.getInTaxRate()
                            + ", 进项未税价格调整 " + contractItem.getExclusiveTaxPrice() + " -> "
                            + costItem.getInExclusiveTaxPrice() + "!");
                }
                return;
            }
        }
        if (!NumberUtils.equals(costItem.getInTaxRate(), contractItem.getTaxRate())) {
            costItem.setInTaxRate(contractItem.getTaxRate());
            holder.warn("更新进项成本的税率为：" + costItem.getInTaxRate());
        }
        if (!NumberUtils.equals(costItem.getInExclusiveTaxPrice(), contractItem.getExclusiveTaxPrice())) {
            costItem.setInExclusiveTaxPrice(contractItem.getExclusiveTaxPrice());
            holder.warn("更新进项成本的未税价格为：" + costItem.getInExclusiveTaxPrice());
        }
    }

    /**
     * 合并合同条目为一条成本条目
     *
     * @param inventory     存货
     * @param contractItems 合同中的条目
     * @param costItems     成本表中的条目
     * @param results       最总输出的成本表条目列表
     */
    private void compositeOut(
            Inventory inventory,
            List<ContractItem> contractItems, List<ProjectCostItem> costItems,
            List<ProjectCostItem> results) {
        // 合计总金额
        double totalFee = contractItems.stream().mapToDouble(v -> v.getTaxPrice() * v.getQuantity()).sum();
        // 如果有存货，则使用存货的税率，否则使用13%
        float taxRate = inventory == null ? TaxRateUtils.TAX_RATE_GENERAL : inventory.getPurchasePrice().getTaxRate();
        if (taxRate >= TaxRateUtils.TAX_RATE_GENERAL_OLDEST
                && TaxRateUtils.isAfterTaxRateChangeTo13(cost.getApplyTime())) {
            taxRate = TaxRateUtils.TAX_RATE_GENERAL;
        }
        // 合计数量
        double totalQuantity = contractItems.stream().mapToDouble(ContractItem::getQuantity).sum();
        // 计算含税采购单价
        double taxPrice = totalQuantity == 0 ? totalFee : (totalFee / totalQuantity);
        // 计算不含税采购单价
        double outExclusiveTaxPrice = TaxRateUtils.toExclusiveTax(taxPrice, taxRate);

        ProjectCostItem projectCostItem = null;
        if (costItems.isEmpty()) {
            // 成本条目不存在，则创建成本条目
            projectCostItem = new ProjectCostItem();
            projectCostItem.setCost(cost);
            projectCostItem.setInventory(inventory);
            if (inventory != null) {
                projectCostItem.setTitle(inventory.getName());
                projectCostItem.setSpecification(inventory.getSpecification());
                projectCostItem.setUnit(inventory.getUnit());
            } else {
                ContractItem first = contractItems.getFirst();
                projectCostItem.setTitle(first.getTitle());
                projectCostItem.setSpecification(first.getSpecification());
                projectCostItem.setUnit(first.getUnit());
            }
            projectCostItem.setCreator(currentUser.get());
            projectCostItem.setCreateDate(LocalDateTime.now());
            projectCostItem = save(projectCostItem);
            results.add(projectCostItem);
        } else {
            projectCostItem = costItems.getFirst();
            projectCostItem.setUpdater(currentUser.get());
            projectCostItem.setUpdateDate(LocalDateTime.now());
        }

        projectCostItem.setOutTaxRate(taxRate);
        projectCostItem.setOutExclusiveTaxPrice(outExclusiveTaxPrice);
        projectCostItem.setOutQuantity(totalQuantity);

        save(projectCostItem);
    }

    ProjectCostService getCostService() {
        if (costService == null) {
            costService = getBean(ProjectCostService.class);
        }
        return costService;
    }

    public ProjectCostItemService getItemService() {
        if (itemService == null) {
            itemService = getBean(ProjectCostItemService.class);
        }
        return itemService;
    }

    ContractService getContractService() {
        if (contractService == null) {
            contractService = getBean(ContractService.class);
        }
        return contractService;
    }

    ContractItemService getContractItemService() {
        if (contractItemService == null) {
            contractItemService = getBean(ContractItemService.class);
        }
        return contractItemService;
    }

    InventoryService getInventoryService() {
        if (inventoryService == null) {
            inventoryService = getBean(InventoryService.class);
        }
        return inventoryService;
    }

    public ProjectCostItem save(ProjectCostItem entity) {
        return getItemService().save(entity);
    }

}
