/**
 * @filename:PurchaseOrderServiceImpl 2024-09-10
 * @project KJT v1.0.0 Copyright(c) 2024 wanglang Co. Ltd. All right reserved.
 */
package com.trade.service.order.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import com.trade.common.utils.ConstantUtil;
import com.trade.common.utils.LoggerUtil;
import com.trade.common.utils.StringUtils;
import com.trade.common.utils.bean.BeanCopyUtils;
import com.trade.common.utils.bean.Builder;
import com.trade.service.DictConstants;
import com.trade.service.FileTypeEnum;
import com.trade.service.TransStruct;
import com.trade.service.companyrelated.domain.VendorEntity;
import com.trade.service.companyrelated.service.IVendorEntityService;
import com.trade.service.contract.dto.ContractGenInputItem;
import com.trade.service.contract.dto.ContractGenOutputItem;
import com.trade.service.contract.service.ContractService;
import com.trade.service.contract.service.ISFileTemplateService;
import com.trade.service.enterprise.domain.Enterprise;
import com.trade.service.enterprise.domain.EnterpriseRelatedPerson;
import com.trade.service.enterprise.service.EnterpriseBaseService;
import com.trade.service.enterprise.service.EnterpriseRelatedPersonBaseService;
import com.trade.service.filestorage.entity.BusinessRelateFile;
import com.trade.service.filestorage.entity.TFileStorage;
import com.trade.service.filestorage.service.BusinessRelateFileBaseService;
import com.trade.service.filestorage.service.TFileStorageService;
import com.trade.service.filestorage.util.oss.OSSUtil;
import com.trade.service.message.EmailTemplate;
import com.trade.service.order.domain.PurchaseOrder;
import com.trade.service.order.domain.PurchaseOrderCommodity;
import com.trade.service.order.domain.PurchaseOrderInvoice;
import com.trade.service.order.domain.PurchaseOrderLogistics;
import com.trade.service.order.dto.PurchaseOrderCommodityTL;
import com.trade.service.order.dto.PurchaseOrderDto;
import com.trade.service.order.dto.PurchaseOrderLogisticsDto;
import com.trade.service.order.dto.PurchaseSearchDto;
import com.trade.service.order.mapper.PurchaseOrderMapper;
import com.trade.service.order.service.PurchaseOrderCommodityService;
import com.trade.service.order.service.PurchaseOrderInvoiceService;
import com.trade.service.order.service.PurchaseOrderLogisticsService;
import com.trade.service.order.service.PurchaseOrderService;
import com.trade.service.order.vo.PurchaseAcountVo;
import com.trade.service.order.vo.PurchaseOrderDetailsVo;
import com.trade.service.order.vo.PurchaseOrderLogisticsVo;
import com.trade.service.order.vo.PurchaseOrderVo;
import com.trade.service.purchase.domain.PurchaseCommodity;
import com.trade.service.purchase.domain.PurchaseContract;
import com.trade.service.purchase.service.PurchaseCommodityBaseService;
import com.trade.service.purchase.service.PurchaseContractBaseService;
import com.trade.service.sales.domain.ContractTerms;

import com.trade.service.sales.domain.SalesContract;
import com.trade.service.sales.service.ContractTermsService;
import com.trade.service.sales.service.SalesContractService;
import com.trade.service.salesorder.constant.SaleOrderConstant;
import com.trade.service.salesorder.domain.SalesOrder;

import com.trade.service.salesorder.service.ISalesOrderService;
import com.trade.service.serialNum.service.SerialNumberGenerator;
import com.trade.service.tradingPlatform.domain.TradingPlatform;
import com.trade.service.tradingPlatform.service.TradingPlatformBaseService;
import org.slf4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.InputStream;
import java.math.BigDecimal;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * @Description: 订单信息服务层实现类
 * @version: v1.0.0
 * @author: wanglang
 */
@Service
public class PurchaseOrderServiceImpl extends ServiceImpl<PurchaseOrderMapper, PurchaseOrder> implements PurchaseOrderService {

    private static final Logger log = LoggerUtil.logger(LoggerUtil.LogFileNameEnum.SERVICES_LOG);
    @Resource
    private PurchaseOrderLogisticsService purchaseOrderLogisticsService;
    @Resource
    private ISalesOrderService salesOrderService;
    @Resource
    private SalesContractService salesContractService;
    @Resource
    private PurchaseContractBaseService purchaseContractBaseService;
    @Resource
    private PurchaseOrderCommodityService purchaseOrderCommodityService;
    @Resource
    private BusinessRelateFileBaseService businessRelateFileBaseService;
    @Resource
    private SerialNumberGenerator serialNumberGenerator;
    @Resource
    private ContractTermsService contractTermsService;
    @Resource
    private PurchaseCommodityBaseService purchaseCommodityBaseService;
    @Resource
    private ISFileTemplateService fileTemplateService;
    @Resource
    private EnterpriseBaseService enterpriseBaseService;
    @Resource
    private TradingPlatformBaseService tradingPlatformBaseService;
    @Resource
    private EnterpriseRelatedPersonBaseService relatedPersonBaseService;
    @Resource
    private TransStruct transStruct;
    @Resource
    private ContractService contractService;
    @Resource
    private PurchaseOrderInvoiceService purchaseOrderInvoiceService;
    @Resource
    private EmailTemplate emailTemplate;
    @Resource
    private IVendorEntityService vendorEntityService;
    @Resource
    private OSSUtil ossUtil;
    @Resource
    private TFileStorageService tFileStorageService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrderLogistics(PurchaseOrderLogisticsDto purchaseOrderLogisticsDto) {
        PurchaseOrderLogistics purchaseOrderLogistics = BeanCopyUtils.copy(purchaseOrderLogisticsDto,
                PurchaseOrderLogistics.class);
        BusinessRelateFile businessRelateFile = Builder.of(BusinessRelateFile.class)
                .with(BusinessRelateFile::setBelongId, purchaseOrderLogistics.getPurchaseOrderId())
                .with(BusinessRelateFile::setBelongType, ConstantUtil.PURCHASE_ORDER)
                .with(BusinessRelateFile::setFileType, FileTypeEnum.PURCHASE_ORDER_TRANS_PROVE.getCode())
                .with(BusinessRelateFile::setFileId, purchaseOrderLogisticsDto.getTransportCertificateFileId()).build();
        purchaseOrderLogisticsService.save(purchaseOrderLogistics);
        businessRelateFileBaseService.upsertBusinessRelateFiles(businessRelateFile);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrder(List<PurchaseOrderDto> purchaseOrderDtos) {
        for (PurchaseOrderDto purchaseOrderDto : purchaseOrderDtos) {
            PurchaseOrder purchaseOrder = new PurchaseOrder();
            purchaseOrder.setSalesOrderId(purchaseOrderDto.getSalesOrderId());
            purchaseOrder.setCustomerUserId(purchaseOrderDto.getCustomerUserId());
            purchaseOrder.setSalesOrderId(purchaseOrderDto.getSalesOrderId());
            purchaseOrder.setEntityNo(purchaseOrderDto.getVendorEntityNo());
            purchaseOrder.setDeleted(0);
            List<PurchaseOrderCommodity> commodities = purchaseOrderDto.getCommodities();
            BigDecimal amount = commodities.stream().map(PurchaseOrderCommodity::getAmount).reduce(BigDecimal::add)
                    .orElse(BigDecimal.ZERO);
            BigDecimal taxRefundRate = commodities.stream()
                    .map(s -> s.getAmount().multiply(s.getTaxRefundRate()).multiply(new BigDecimal(s.getNumber())))
                    .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            purchaseOrder.setAmountTotal(amount);
            purchaseOrder.setTaxAmountTotal(taxRefundRate);
            SalesOrder salesOrder = salesOrderService.getById(purchaseOrderDto.getSalesOrderId());
            SalesContract salesContract = salesContractService.getById(salesOrder.getSaleContractId());
            purchaseOrder.setAgencyCertNo(salesContract.getAgencyCertNo());
            PurchaseContract purchaseContract;
            if (StringUtils.isNotEmpty(purchaseOrder.getEntityNo())) {
                purchaseContract = purchaseContractBaseService.lambdaQuery()
                        .eq(PurchaseContract::getSalesContractNo, salesContract.getContractNo())
                        .eq(PurchaseContract::getVendorEntityNo, purchaseOrder.getEntityNo())
                        .eq(PurchaseContract::getDeleted, 0).one();
            } else {
                purchaseContract = purchaseContractBaseService.lambdaQuery()
                        .eq(PurchaseContract::getSalesContractNo, salesContract.getContractNo())
                        .eq(PurchaseContract::getDeleted, 0).one();
            }
            purchaseOrder.setPurchaseContractNo(purchaseContract.getContractNo());
            ContractTerms contractTerms = contractTermsService.getByContractIdAndType(purchaseContract.getId(),
                    ConstantUtil.SALES_CONTRACT_NO_PREFIX);
            purchaseOrder.setInvoiceType(contractTerms.getInvoiceType());
            purchaseOrder.setSettlementCycle(contractTerms.getSettlementCycle());
            purchaseOrder.setOrderNo(
                    serialNumberGenerator.nextValue(ConstantUtil.PURCHASE_ORDER_NO_PREFIX, salesOrder.getOrderNo()));
            List<PurchaseCommodity> purchaseCommodities = purchaseCommodityBaseService.lambdaQuery()
                    .eq(PurchaseCommodity::getPurchaseContractId, purchaseContract.getId())
                    .eq(PurchaseCommodity::getSalesContractId, 0).eq(PurchaseCommodity::getDeleted, 0).list();
            for (PurchaseOrderCommodity commodity : commodities) {
                PurchaseCommodity purchaseCommodity = purchaseCommodities.stream()
                        .filter(p -> p.getSalesCommodityId().equals(commodity.getSalesCommodityId())).findAny()
                        .orElse(null);
                if (purchaseCommodity != null) {
                    commodity.setPriceUnit(purchaseCommodity.getPriceUnit());
                }
            }
            this.baseMapper.insert(purchaseOrder);
            purchaseOrderCommodityService.saveBatch(commodities);
            CompletableFuture.runAsync(() -> handleInvoiceNotice(purchaseOrder, commodities, contractTerms))
                    .exceptionally(e -> {
                        log.error("customerUserId={}orderNo={},处理开票通知书失败:{}", purchaseOrder.getCustomerUserId(),
                                purchaseOrder.getOrderNo(), e);
                        return null;
                    });
        }
        return true;
    }

    @Override
    public List<PurchaseOrderVo> selectByCondition(PurchaseSearchDto purchaseSearch) {
        return this.baseMapper.selectByCondition(purchaseSearch);
    }

    @Override
    public PurchaseOrderDetailsVo getDetailsById(Long id) {
        PurchaseOrderDetailsVo purchaseOrderDetailsVo = this.baseMapper.selectDetailsById(id);
        List<PurchaseOrderCommodity> commodities = purchaseOrderCommodityService.lambdaQuery()
                .eq(PurchaseOrderCommodity::getPurchaseOrderId, id).list();
        purchaseOrderDetailsVo.setCommodityList(commodities);
        PurchaseOrderLogisticsVo purchaseOrderLogisticsVo = purchaseOrderLogisticsService.queryByPurchaseOrderId(id);
        purchaseOrderDetailsVo.setLogisticsVo(purchaseOrderLogisticsVo);
        BusinessRelateFile businessRelateFile = businessRelateFileBaseService.getByCondition(
                purchaseOrderLogisticsVo.getId(), ConstantUtil.PURCHASE_ORDER,
                FileTypeEnum.PURCHASE_ORDER_TRANS_PROVE.getCode());
        purchaseOrderLogisticsVo.setId(businessRelateFile.getId());
        PurchaseContract purchaseContract = purchaseContractBaseService.lambdaQuery()
                .eq(PurchaseContract::getContractNo, purchaseOrderDetailsVo.getPurchaseContractNo())
                .eq(PurchaseContract::getDeleted, 0).one();
        ContractTerms contractTerms = contractTermsService.getByContractIdAndType(purchaseContract.getId(),
                ConstantUtil.PURCHASE_CONTRACT_CODE);
        PurchaseAcountVo purchaseAcountVo = new PurchaseAcountVo();
        purchaseAcountVo.setAccountName(contractTerms.getAccountName());
        purchaseAcountVo.setAccountNumber(contractTerms.getAccountNumber());
        purchaseAcountVo.setOpeningBank(contractTerms.getOpeningBank());
        purchaseOrderDetailsVo.setPurchaseAcountVo(purchaseAcountVo);
        return purchaseOrderDetailsVo;
    }

    private void handleInvoiceNotice(PurchaseOrder purchaseOrder, List<PurchaseOrderCommodity> commodities,
            ContractTerms contractTerms) {
        ContractGenInputItem contractGenInputItem = new ContractGenInputItem();
        String templateCode = fileTemplateService.selectByFileType(FileTypeEnum.PURCHASE_ORDER_INVOICE_NOTICE.getCode())
                .getTemplateCode();
        contractGenInputItem.setTemplateCode(templateCode);
        Enterprise enterprise = enterpriseBaseService.getEnterpriseByCustomerId(purchaseOrder.getCustomerUserId());
        EnterpriseRelatedPerson contact = relatedPersonBaseService
                .getContactByChangeRecordId(enterprise.getChangeRecordId());
        String customerName = enterprise.getName();
        String customerAddress = enterprise.getBusinessAddress();
        String contractTel = contact.getMobile();
        String certNo = enterprise.getCertNo();
        String receiver;
        if (DictConstants.EXPORT_TYPE_PROXY.equals(enterprise.getExportType())) {
            TradingPlatform tradingPlatform = tradingPlatformBaseService.lambdaQuery()
                    .eq(TradingPlatform::getCertNo, purchaseOrder.getAgencyCertNo()).one();
            customerName = tradingPlatform.getPlatformName();
            customerAddress = tradingPlatform.getBusinessAddress();
            contractTel = tradingPlatform.getContactMobile();
            certNo = tradingPlatform.getCertNo();
            receiver = contact.getEmail();
        } else {
            VendorEntity entity = vendorEntityService.getByEntityNo(purchaseOrder.getEntityNo());
            receiver = entity.getContactEmail();
        }
        String bankAndAccount = contractTerms.getOpeningBank() + "、" + contractTerms.getAccountNumber();
        Map<String, Object> params = new HashMap<>();
        params.put("invoiceType", purchaseOrder.getInvoiceType());
        params.put("enterpriseName", customerName);
        params.put("certNo", certNo);
        params.put("addressPhone", customerAddress + "、" + contractTel);
        params.put("bankAndAccount", bankAndAccount);
        params.put("total", purchaseOrder.getAmountTotal());
        params.put("taxTotal", purchaseOrder.getTaxAmountTotal());
        params.put("purchaseOrderNo", purchaseOrder.getOrderNo());
        List<PurchaseOrderCommodityTL> commoditys = transStruct.transPurchaseOrderCommodityForTLList(commodities);
        params.put(SaleOrderConstant.ORDER_INVOICE_GOODS_LIST_KEY, commoditys);
        contractGenInputItem.setParams(params);
        LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
        contractGenInputItem
                .setConfig(Configure.builder().bind(SaleOrderConstant.ORDER_INVOICE_GOODS_LIST_KEY, policy).build());
        ContractGenOutputItem outputItem = contractService.generateSingleContract(contractGenInputItem,
                purchaseOrder.getCustomerUserId());
        PurchaseOrderInvoice purchaseOrderInvoice = new PurchaseOrderInvoice();
        purchaseOrderInvoice.setOrderId(purchaseOrder.getId());
        purchaseOrderInvoice.setFileId(outputItem.getFileId());
        purchaseOrderInvoice.setAmount(purchaseOrder.getAmountTotal());
        purchaseOrderInvoiceService.save(purchaseOrderInvoice);
        CompletableFuture.runAsync(() -> sendEmail(receiver, outputItem.getFileId())).exceptionally(e -> {
            log.error("customerUserId={}orderNo={},发送邮件失败:{}", purchaseOrder.getCustomerUserId(),
                    purchaseOrder.getOrderNo(), e);
            return null;
        });
    }

    /**
     * 发送邮件
     * 
     * @param receiver
     * @param fileId
     */
    private void sendEmail(String receiver, Long fileId) {
        TFileStorage tFileStorage = tFileStorageService.getFileStorageById(fileId);
        InputStream in = ossUtil.downloadFile(tFileStorage.getPath());
        // todo 缺少模板
        emailTemplate.sendHtmlMail("", "", null, tFileStorage.getOriginalName(), in, receiver);
    }
}
