package com.bcxin.tenant.open.domains.services.impls;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.bcxin.tenant.open.domains.entities.BillPaymentRecordEntity;
import com.bcxin.tenant.open.domains.entities.MonthlyBillEntity;
import com.bcxin.tenant.open.domains.entities.OrgPurseEntity;
import com.bcxin.tenant.open.domains.entities.OrgPurseTransactionEntity;
import com.bcxin.tenant.open.domains.repositories.BillPaymentRecordRepository;
import com.bcxin.tenant.open.domains.repositories.MonthlyBillRepository;
import com.bcxin.tenant.open.domains.repositories.OrgPurseRepository;
import com.bcxin.tenant.open.domains.repositories.OrgPurseTransactionRepository;
import com.bcxin.tenant.open.domains.services.OrgPurseTransactionService;
import com.bcxin.tenant.open.domains.services.commands.GenerateMonthlyBillOrgPurseTransactionCommand;
import com.bcxin.tenant.open.domains.services.commands.CreateOrgPurseTransactionCommand;
import com.bcxin.tenant.open.infrastructures.UnitWork;
import com.bcxin.tenant.open.domains.services.commands.RechargeOrgPurseTransactionCommand;
import com.bcxin.tenant.open.domains.utils.OrgPurseTransactionUtil;
import com.bcxin.tenant.open.infrastructures.UnitWork;
import com.bcxin.tenant.open.infrastructures.components.IdWorker;
import com.bcxin.tenant.open.infrastructures.components.JsonProvider;
import com.bcxin.tenant.open.infrastructures.components.RetryProvider;
import com.bcxin.tenant.open.infrastructures.enums.BillPaymentStatus;
import com.bcxin.tenant.open.infrastructures.exceptions.RetryableTenantException;
import lombok.Synchronized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.sql.Timestamp;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class OrgPurseTransactionServiceImpl implements OrgPurseTransactionService {
    private final Logger logger = LoggerFactory.getLogger(OrgPurseTransactionServiceImpl.class);
    private final OrgPurseTransactionRepository orgPurseTransactionRepository;
    private final OrgPurseRepository orgPurseRepository;
    private final MonthlyBillRepository monthlyBillRepository;
    private final BillPaymentRecordRepository billPaymentRecordRepository;
    private final IdWorker idWorker;
    private final JsonProvider jsonProvider;
    private final RetryProvider retryProvider;
    private final UnitWork unitWork;

    public OrgPurseTransactionServiceImpl(OrgPurseTransactionRepository orgPurseTransactionRepository,
                                          OrgPurseRepository orgPurseRepository,
                                          MonthlyBillRepository monthlyBillRepository, BillPaymentRecordRepository billPaymentRecordRepository, IdWorker idWorker,
                                          JsonProvider jsonProvider, RetryProvider retryProvider, UnitWork unitWork) {
        this.orgPurseTransactionRepository = orgPurseTransactionRepository;
        this.orgPurseRepository = orgPurseRepository;
        this.monthlyBillRepository = monthlyBillRepository;
        this.billPaymentRecordRepository = billPaymentRecordRepository;
        this.idWorker = idWorker;
        this.jsonProvider = jsonProvider;
        this.retryProvider = retryProvider;
        this.unitWork = unitWork;
    }

    @Override
    public void dispatch(CreateOrgPurseTransactionCommand command) {
        String tranId = this.unitWork.beginTransaction();
        try {
            this.orgPurseTransactionRepository.batchInsert(command.getOrgPurseTransactions());

            this.unitWork.commit(tranId);
        } catch (Exception ex) {
            this.unitWork.rollback(tranId);
            throw ex;
        }
    }

    /**
     * 月度账单生成触发付费，要把所有的欠费账单提出来，极端情况下有补漏数据（之前月份的）
     *
     * @param command
     */

    @Override
    public void dispatch(GenerateMonthlyBillOrgPurseTransactionCommand command) {
        List<MonthlyBillEntity> billEntities = monthlyBillRepository.getRequiredPaymentBills();
        chargeBills(billEntities);
    }

    @Override
    public void monthlyBillCharge(RechargeOrgPurseTransactionCommand command) {
        Collection<String> orgIds = command.getOrgIds();
        List<MonthlyBillEntity> billEntities = monthlyBillRepository.getRequiredPaymentBillsByOrgs(orgIds);
        chargeBills(billEntities);
    }

    /**
     * 根据账单扣除费用
     *
     *为了防止在账单生成后，触发扣款的同时，公司此时也充值了，同时触发扣款。
     * @param billEntities
     */
    @Synchronized
    protected void chargeBills(List<MonthlyBillEntity> billEntities) {
        if (CollectionUtils.isEmpty(billEntities)) {
            return;
        }

        final Map<String, List<MonthlyBillEntity>> billsMap = billEntities.stream()
                .collect(Collectors.groupingBy(MonthlyBillEntity::getOrgId));

        final Collection<OrgPurseEntity> orgPurses =
                this.orgPurseRepository.getByOrganizationIds(billsMap.keySet());

        //以后可能产生没有钱包的企业也会产生账单，这里只有有钱包的企业才能支付账单。
        for (OrgPurseEntity orgPurse : orgPurses) {
            try {
                retryProvider.doExecute(() -> {
                    //防止钱包的金额在扣除账单的时候被修改，每次扣除账单之前，都取最新的钱包状态。
                    Optional<OrgPurseEntity> orgPurseOptional =
                                   this.orgPurseRepository.getByOrganizationIds(Arrays.asList(orgPurse.getOrganizationId())).stream().findFirst();
                    if(orgPurseOptional.isPresent()){
                        chargeOrganizationBills(billsMap.get(orgPurse.getOrganizationId()), orgPurseOptional.get());
                    }
                    return null;
                });
            } catch (Exception e) {
                logger.error("公司{}的月度账单自动扣费失败", orgPurse.getOrganizationName(), e);
            }
        }
    }

    @Override
    public void chargeOrganizationBills(List<MonthlyBillEntity> billsList, OrgPurseEntity orgPurse) {
        if(CollectionUtils.isEmpty(billsList) || orgPurse.getPoints() <= 0) return;
        String transactionId = unitWork.beginTransaction();
        try {
//            OrgPurseEntity cloneOrgPurse = orgPurse.clone();
            long oldPoints = orgPurse.getPoints();
            long remainPoints = oldPoints;
            List<MonthlyBillEntity> bills = billsList.stream().map(ix-> {
                try {
                    return ix.clone();
                } catch (CloneNotSupportedException e) {
                    throw new RuntimeException(e);
                }
            }).collect(Collectors.toList());
            List<OrgPurseTransactionEntity> orgPurseTransactions = new ArrayList<>();
            List<MonthlyBillEntity> updatedBills = new ArrayList<>();
            List<BillPaymentRecordEntity> billPaymentRecordEntities = new ArrayList<>();
            for (MonthlyBillEntity bill : bills) {
                if (remainPoints <= 0) break;
                OrgPurseTransactionEntity transaction =
                        OrgPurseTransactionUtil.build(idWorker, bill, jsonProvider, remainPoints, orgPurse.getOrganizationName());
                remainPoints += transaction.getPoints();
                bill.setPaidAmount(bill.getPaidAmount() - transaction.getPoints().intValue());
                bill.setPaidTime(Timestamp.from(Instant.now()));
                bill.setBillPaymentStatus(bill.getPaidAmount() == 0 ? BillPaymentStatus.Unpaid :
                        bill.getPaidAmount().equals(bill.getAmount()) ? BillPaymentStatus.Paid :
                                BillPaymentStatus.PartialPaid);
                BillPaymentRecordEntity paymentRecord =
                        BillPaymentRecordEntity.create(bill.getBillId(),
                                0 - transaction.getPoints().intValue(),
                                bill.getOrgId());
                orgPurseTransactions.add(transaction);
                updatedBills.add(bill);
                billPaymentRecordEntities.add(paymentRecord);
            }
            this.orgPurseTransactionRepository.batchInsertWithoutUpdateOrgPurse(orgPurseTransactions);
            this.monthlyBillRepository.updatePaidAmount(updatedBills);
            this.billPaymentRecordRepository.batchInsert(billPaymentRecordEntities);

            orgPurse.addPoints(remainPoints - oldPoints, orgPurseTransactions.stream()
                    .map(ix -> ix.getId()).collect(Collectors.joining(";")));
            LambdaUpdateWrapper<OrgPurseEntity> wrapper = new LambdaUpdateWrapper<>();
            wrapper.eq(OrgPurseEntity::getPoints, oldPoints);
            wrapper.eq(OrgPurseEntity::getId, orgPurse.getId());
            int affectedCount = orgPurseRepository.update(orgPurse, wrapper);
            if (affectedCount <= 0) {
                throw new RetryableTenantException("更新企业钱包失败");
            }
            unitWork.commit(transactionId);
        } catch (Exception e) {
            unitWork.rollback(transactionId);
            throw new RetryableTenantException(String.format("月度账单自动扣费异常,公司：%s", orgPurse.getOrganizationId()), e);
        }

    }
}
