package com.yuchen.saas.park.service.service.property;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.thinkunion.park.service.common.utils.AmtUtil;
import com.yuchen.saas.manage.user.api.entity.User;
import com.yuchen.saas.manage.user.api.feign.IUserClient;
import com.yuchen.saas.park.api.dto.property.*;
import com.yuchen.saas.park.api.entity.property.*;
import com.yuchen.saas.park.api.enums.property.*;
import com.yuchen.saas.park.api.vo.property.PropertyBusinessChargesListVo;
import com.yuchen.saas.park.api.vo.property.PropertyBusinessChargesViewVo;
import com.yuchen.saas.park.api.vo.property.PropertyCustomerArchivesVo;
import com.yuchen.saas.park.service.event.property.*;
import com.yuchen.saas.park.service.mapper.property.PropertyBusinessChargesMapper;
import com.yuchen.saas.park.service.service.property.bill.PropertyBillFactoryService;
import com.yuchen.saas.park.service.service.property.bill.PropertyBillSearchService;
import com.yuchen.saas.park.service.service.property.bill.PropertyBillService;
import com.yuchen.saas.park.service.util.ManualValidUtil;
import org.nest.springwrap.core.log.exception.ServiceException;
import org.nest.springwrap.core.secure.utils.AuthUtil;
import org.nest.springwrap.core.tool.api.R;
import org.nest.springwrap.core.tool.utils.Func;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.groups.Default;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class PropertyBusinessChargesService extends ServiceImpl<PropertyBusinessChargesMapper, PropertyBusinessCharges> {
    @Resource
    private ManualValidUtil manualValidUtil;
    @Resource
    private PropertyFeeStandardService propertyFeeStandardService;
    @Resource
    private PropertyBusinessChargesDetailService propertyBusinessChargesDetailService;
    @Resource
    private PropertyBusinessChargesBillService propertyBusinessChargesBillService;
    @Resource
    private PropertyBillFactoryService propertyBillFactoryService;
    @Resource
    private PropertyPaymentRefundAccountInfoService propertyPaymentRefundAccountInfoService;
    @Resource
    private PropertyBillService propertyBillService;
    @Resource
    private PropertyBillSearchService propertyBillSearchService;
    @Resource
    private ApplicationContext applicationContext;
    @Resource
    private PropertySearchParkInfoService propertySearchParkInfoService;
    @Resource
    private IPropertyCustomerArchivesService propertyCustomerArchivesService;
    @Resource
    private IPropertyOfflinePaymentRecordService offlinePaymentRecordService;
    @Resource
    private IUserClient userClient;

    /**
     * 经营性收费-收款提交
     *
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveBusiness(PropertyBillBusinessDto dto) {
        this.validParam(dto);
        this.verifySaveBusiness(dto);
        PropertyBusinessCharges po = this.assemble(dto);
        this.saveOrUpdate(po);

        propertyBusinessChargesDetailService.removeDetail(po.getId());
        propertyPaymentRefundAccountInfoService.removeAccountInfo(po.getId(), AccountBizTypeEnum.BUSINESS, RecordTypeEnum.INCOME);

        List<PropertyBusinessChargesDetail> detailList = BeanUtil.copyToList(dto.getBusinessBillList(), PropertyBusinessChargesDetail.class);
        detailList.forEach(detail -> {
            detail.setBusinessChargesId(po.getId());
            detail.setCustomerId(po.getCustomerId());
            detail.setReceivableDate(dto.getReceivableDate());
            detail.setCreateTime(new Date());
        });
        propertyBusinessChargesDetailService.saveBatch(detailList);

        // 收款账户信息
        if (dto.getChargeType() == YesOrNoEnum.YES.getValue()) {
            PropertyPaymentRefundAccountInfo accountInfo = BeanUtil.copyProperties(dto, PropertyPaymentRefundAccountInfo.class);
            accountInfo.setRecordType(RecordTypeEnum.INCOME.getValue());
            accountInfo.setRetraceId(po.getId());
            accountInfo.setAccountBizType(AccountBizTypeEnum.BUSINESS.getValue());
            accountInfo.setCreateTime(new Date());
            accountInfo.setCreateUser(AuthUtil.getUserId());
            propertyPaymentRefundAccountInfoService.save(accountInfo);

            PropertyOfflinePaymentRecordDto recordDto = BeanUtil.copyProperties(accountInfo, PropertyOfflinePaymentRecordDto.class);
            recordDto.setRetraceId(po.getId());
            recordDto.setParkId(po.getParkId());
            recordDto.setCustomerId(dto.getCustomerId());
            recordDto.setReceivableAmtFen(dto.getNoPayAmtFen());
            recordDto.setPayAmtFen(dto.getPayAmtFen());
            applicationContext.publishEvent(new NotifyOfflinePaymentEvent(recordDto));
        }

        if (po.getStatus() == DepositStatusEnum.CHARGED.getValue()) {
            // 线上收费生成已出账单
            List<PropertyBusinessCreateBillDto> billList = new ArrayList<>();
            dto.getBusinessBillList().forEach(item -> {
                PropertyBusinessCreateBillDto createBillDto = BeanUtil.copyProperties(item, PropertyBusinessCreateBillDto.class);
                createBillDto.setCustomerId(dto.getCustomerId());
                createBillDto.setReceivableDate(dto.getReceivableDate());
                createBillDto.setBusinessChargesId(po.getId());
                billList.add(createBillDto);
            });
            CreateBillDto createBillDto = CreateBillDto.create();
            createBillDto.setBusinessCreateBillDtoList(billList);
            createBillDto.setCreateBillTypeEnum(CreateBillTypeEnum.BUSINESS);
            propertyBillFactoryService.createBill(createBillDto);
        }

    }

    @EventListener(condition = "#event.createBillTypeEnum.value == 8")
    public void saveBusinessBill(CreateBillSuccessEvent event) {
        PropertyBill bill = propertyBillService.getById(event.getBillId());
        PropertyBusinessChargesBill chargesBill = new PropertyBusinessChargesBill();
        chargesBill.setBusinessChargesId(bill.getRetraceId());
        chargesBill.setBillId(event.getBillId());
        chargesBill.setCreateTime(new Date());
        propertyBusinessChargesBillService.save(chargesBill);
    }


    /**
     * 业务校验
     *
     * @param dto
     */
    private void verifySaveBusiness(PropertyBillBusinessDto dto) {
        PropertyBusinessCharges po = this.getById(dto.getId());
        if (Func.isNotEmpty(dto.getId()) &&
                !(po.getStatus() == DepositStatusEnum.CONFIRMED.getValue()
                        || po.getStatus() == DepositStatusEnum.FREEZE.getValue())) {
            throw new ServiceException("当前状态不支持编辑");
        }

        List<Long> feeIdList = dto.getBusinessBillList().stream()
                .map(PropertyBillBusinessDto.BusinessBill::getFeeId).collect(Collectors.toList());
        List<PropertyFeeStandard> feeList = propertyFeeStandardService.listByIds(feeIdList);
        feeList.forEach(fee -> {
            if (!(fee.getFeeMark().equals(FeeMarkEnum.FIXED.getValue())
                    || fee.getFeeMark().equals(FeeMarkEnum.SPECIFY.getValue()))) {
                throw new ServiceException("临时收费列表只能选择，固定金额或指金额收费标准！");
            }
        });

        if (dto.getChargeType() == YesOrNoEnum.YES.getValue() && dto.getPayAmt().compareTo(dto.getNoPayAmt()) < 0) {
            throw new ServiceException("实收金额不能小于合计应收金额");
        }

    }

    /**
     * 参数校验
     *
     * @param dto
     */
    private void validParam(PropertyBillBusinessDto dto) {
        List<Class> groupList = CollUtil.newArrayList(Default.class);
        if (dto.getChargeType() == YesOrNoEnum.YES.getValue()) {
            if (Func.isEmpty(dto.getPayType())) {
                throw new ServiceException("付款方式不能为空");
            }
            groupList.add(PropertyPaymentRefundAccountDto.Default.class);
            groupList.add(PropertyBillBusinessDto.Pay.class);
            switch (dto.getPayType()) {
                case 1:
                    groupList.add(PropertyPaymentRefundAccountDto.Cash.class);
                    break;
                case 2:
                    groupList.add(PropertyPaymentRefundAccountDto.Third.class);
                    break;
                case 3:
                    groupList.add(PropertyPaymentRefundAccountDto.Bank.class);
                    break;
            }
        }
        manualValidUtil.valid(dto, groupList);

        dto.getBusinessBillList().forEach(item -> {
            manualValidUtil.valid(item, groupList);
        });
    }

    /**
     * 提交信组装
     *
     * @param dto
     * @return
     */
    private PropertyBusinessCharges assemble(PropertyBillBusinessDto dto) {
        PropertyBusinessCharges po = this.getById(dto.getId());
        if (Func.isNotEmpty(po)) { // 编辑
            BeanUtil.copyProperties(dto, po);
            po.setUpdateTime(new Date());
            po.setUpdateUser(AuthUtil.getUserId());
        } else {
            po = BeanUtil.copyProperties(dto, PropertyBusinessCharges.class);
            if (dto.getChargeType() == YesOrNoEnum.NO.getValue()) {
                po.setStatus(DepositStatusEnum.CHARGED.getValue());
            } else {
                po.setStatus(DepositStatusEnum.CONFIRMED.getValue());
            }
            po.setCreateTime(new Date());
            po.setCreateUser(AuthUtil.getUserId());
        }

        return po;
    }

    public PropertyBillBusinessDto detail(propertyBusinessChargesDetailDto dto) {
        Long businessChagresId = dto.getId();
        PropertyBusinessCharges po = this.getById(businessChagresId);
        if (dto.getIsRefund() == YesOrNoEnum.NO.getValue() && !(po.getStatus() == DepositStatusEnum.CONFIRMED.getValue()
                || po.getStatus() == DepositStatusEnum.FREEZE.getValue())) {
            throw new ServiceException("当前状态不可编辑");
        }
        PropertyBillBusinessDto vo = new PropertyBillBusinessDto();
        if (po.getChargeType() == YesOrNoEnum.YES.getValue()) {
            PropertyPaymentRefundAccountInfo accountInfo = propertyPaymentRefundAccountInfoService
                    .getPaymentRefundAccountInfo(businessChagresId,AccountBizTypeEnum.BUSINESS,RecordTypeEnum.INCOME);
            BeanUtil.copyProperties(accountInfo,vo);
        }
        BeanUtil.copyProperties(po,vo);

        List<PropertyBillBusinessDto.BusinessBill> voList = new ArrayList<>();
        List<Long> feeIdList = new ArrayList<>();
        List<PropertyBusinessChargesDetail> detailList = propertyBusinessChargesDetailService
                .lambdaQuery().eq(PropertyBusinessChargesDetail::getBusinessChargesId,businessChagresId).list();
        detailList.forEach(item -> {
            PropertyBillBusinessDto.BusinessBill dvo = BeanUtil.copyProperties(item,PropertyBillBusinessDto.BusinessBill.class);
            dvo.setReceivableAmt(AmtUtil.fenToYuanObj(item.getReceivableAmtFen()));
            voList.add(dvo);
            feeIdList.add(item.getFeeId());
        });
        applicationContext.publishEvent(new SetFeeNameEvent(voList,feeIdList));

        vo.setNoPayAmt(AmtUtil.fenToYuanObj(po.getNoPayAmtFen()));
        vo.setPayAmt(AmtUtil.fenToYuanObj(po.getPayAmtFen()));
        vo.setBalanceRefundAmt(AmtUtil.fenToYuanObj(po.getPayAmtFen() - po.getRefundAmtFen()));
        vo.setBusinessBillList(voList);

        return vo;
    }


    /**
     * 经营性收费列表
     *
     * @param qryDto
     * @return
     */
    public Page<PropertyBusinessChargesListVo> selectPage(PropertyBusinessQryDto qryDto) {
        Page<PropertyBusinessCharges> page = new Page<>(qryDto.getCurrent(), qryDto.getSize());
        this.page(page, this.getPageQueryParam(qryDto));
        return this.decoratePage(page,qryDto);
    }

    private Page<PropertyBusinessChargesListVo> decoratePage(Page<PropertyBusinessCharges> busiPage,PropertyBusinessQryDto qryDto) {
        Page<PropertyBusinessChargesListVo> page = new Page<>(qryDto.getCurrent(), qryDto.getSize());
        if (busiPage.getRecords().size() == 0) {
            return page;
        }
        List<PropertyBusinessChargesListVo> records = BeanUtil.copyToList(busiPage.getRecords(),PropertyBusinessChargesListVo.class);

        List<Long> userIds = records.stream().map(PropertyBusinessChargesListVo::getCreateUser).collect(Collectors.toList());
        List<Long> parkIdList = records.stream().map(PropertyBusinessChargesListVo::getParkId).filter(Objects::nonNull).collect(Collectors.toList());
        List<Long> customerIdList = records.stream().map(PropertyBusinessChargesListVo::getCustomerId).collect(Collectors.toList());

        applicationContext.publishEvent(new SetCreateUserNameEvent(records,userIds));
        applicationContext.publishEvent(new SetParkNameEvent(records,parkIdList));
        applicationContext.publishEvent(new SetCustomerInfoEvent(records,customerIdList));

        page.setRecords(records);
        page.setTotal(busiPage.getTotal());
        return page;

    }

    private LambdaQueryWrapper<PropertyBusinessCharges> getPageQueryParam(PropertyBusinessQryDto qryDto) {
        LambdaQueryWrapper<PropertyBusinessCharges> qw = new LambdaQueryWrapper<>();
        if (Func.isNotEmpty(qryDto.getParkId())) {
            qw.eq(PropertyBusinessCharges::getParkId,qryDto.getParkId());
        } else {
            qw.in(PropertyBusinessCharges::getParkId, propertySearchParkInfoService.getParkIdsByCurrUser());
        }
        if (Func.isNotEmpty(qryDto.getCustomerId())) {
            qw.eq(PropertyBusinessCharges::getCustomerId, qryDto.getCustomerId());
        }
        if (Func.isNotEmpty(qryDto.getStatus())) {
            if (qryDto.getStatus() == DepositStatusEnum.ARREARS.getValue()) {
                //欠费 TODO tucq
            } else {
                qw.eq(PropertyBusinessCharges::getStatus, qryDto.getStatus());
            }
        }
        if (Func.isNotEmpty(qryDto.getStartPayTime())) {
            qw.le(PropertyBusinessCharges::getPayTime, qryDto.getStartPayTime());
        }
        if (Func.isNotEmpty(qryDto.getEndPayTime())) {
            qw.ge(PropertyBusinessCharges::getPayTime, qryDto.getEndPayTime());
        }
        if (Func.isNotEmpty(qryDto.getStartCreateTime())) {
            qw.le(PropertyBusinessCharges::getCreateTime, qryDto.getStartCreateTime());
        }
        if (Func.isNotEmpty(qryDto.getEndCreateTime())) {
            qw.ge(PropertyBusinessCharges::getCreateTime, qryDto.getEndCreateTime());
        }
        qw.orderByDesc(PropertyBusinessCharges::getCreateTime);
        return qw;
    }


    /**
     * 经营性收费-退款
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    public void refund(PropertyBusinessRefundDto dto) {
        PropertyBusinessCharges po = this.getById(dto.getId());
        if (po.getStatus() != DepositStatusEnum.PAID.getValue()) {
            throw new ServiceException("当前状态不允许退款操作");
        }
        if (dto.getRefundAmtFen() > po.getNoPayAmtFen()) {
            throw new ServiceException("退款金额不能大于可退金额");
        }
        // TODO tucq 参数校验谨密度优化？
        List<Class> groupList = CollUtil.newArrayList(Default.class);
        if (Func.isEmpty(dto.getRefundType())) {
            throw new ServiceException("退款方式不能为空");
        }
        switch (dto.getRefundType()) {
            case 1:
                break;
            case 2:
                groupList.add(PropertyPaymentRefundAccountDto.Third.class);
                break;
            case 3:
                groupList.add(PropertyPaymentRefundAccountDto.Bank.class);
                break;
        }
        manualValidUtil.valid(dto, groupList);

        po.setStatus(DepositStatusEnum.REFUNDED.getValue());
        po.setRefundAmtFen(dto.getRefundAmtFen());
        po.setIsRefund(YesOrNoEnum.YES.getValue());
        po.setRefundTime(dto.getRecordTime());
        po.setRefundRemark(dto.getRefundRemark());
        po.setUpdateTime(new Date());
        po.setUpdateUser(AuthUtil.getUserId());
        this.updateById(po);

        PropertyPaymentRefundAccountInfo accountInfo = BeanUtil.copyProperties(dto, PropertyPaymentRefundAccountInfo.class);
        accountInfo.setId(null);
        accountInfo.setRecordType(RecordTypeEnum.EXPEND.getValue());
        accountInfo.setRetraceId(po.getId());
        accountInfo.setAccountBizType(AccountBizTypeEnum.BUSINESS.getValue());
        accountInfo.setCreateTime(new Date());
        accountInfo.setCreateUser(AuthUtil.getUserId());
        propertyPaymentRefundAccountInfoService.save(accountInfo);

        if (dto.getRefundType() == 1) {
            // TODO tucq 线上退款
        }

    }


    /**
     * 查看
     * @param businessChagresId
     */
    public PropertyBusinessChargesViewVo view(long businessChagresId) {
        PropertyBusinessCharges po = this.getById(businessChagresId);
        PropertyBusinessChargesViewVo vo = BeanUtil.copyProperties(po,PropertyBusinessChargesViewVo.class);
        PropertyCustomerArchivesVo archivesVo = propertyCustomerArchivesService.getCustomerArchivesById(po.getCustomerId());

        List<PropertyBusinessChargesDetail> detailList = propertyBusinessChargesDetailService
                .lambdaQuery().eq(PropertyBusinessChargesDetail::getBusinessChargesId,businessChagresId).list();
        List<Long> feeIdList = detailList.stream().map(PropertyBusinessChargesDetail::getFeeId).collect(Collectors.toList());
        applicationContext.publishEvent(new SetFeeNameEvent(detailList,feeIdList));

        PropertyPaymentRefundAccountInfo payInfo = propertyPaymentRefundAccountInfoService
                .getPaymentRefundAccountInfo(businessChagresId,AccountBizTypeEnum.BUSINESS,RecordTypeEnum.INCOME);
        PropertyBusinessChargesViewVo.PayDataInfo payDataInfo = new PropertyBusinessChargesViewVo.PayDataInfo();
        if (payInfo != null) {
            BeanUtil.copyProperties(payInfo,payDataInfo);
        }
        payDataInfo.setNoPayAmtFen(po.getNoPayAmtFen());
        payDataInfo.setPayAmtFen(po.getPayAmtFen());
        payDataInfo.setPayRemark(po.getPayRemark());
        payDataInfo.setCreateUser(po.getCreateUser());
        R<User> resUser = userClient.getUserById(po.getCreateUser());
        payDataInfo.setCreateUserName(resUser.getData() == null ? "" : resUser.getData().getName());
        payDataInfo.setCreateTime(po.getCreateTime());

        PropertyPaymentRefundAccountInfo refundInfo = propertyPaymentRefundAccountInfoService
                .getPaymentRefundAccountInfo(businessChagresId,AccountBizTypeEnum.BUSINESS,RecordTypeEnum.EXPEND);
        PropertyBusinessChargesViewVo.RefundDataInfo refundDataInfo = new PropertyBusinessChargesViewVo.RefundDataInfo();
        if (refundInfo != null) {
            BeanUtil.copyProperties(refundInfo,refundDataInfo);
        }
        refundDataInfo.setRefundAmtFen(po.getRefundAmtFen());
        refundDataInfo.setRefundRemark(po.getRefundRemark());

        vo.setParkName(archivesVo.getParkName());
        vo.setCustomerName(archivesVo.getCustomerName());
        vo.setDetailList(detailList);
        vo.setPayDataInfo(payDataInfo);
        vo.setRefundDataInfo(refundDataInfo);

        return vo;
    }

    /**
     * 经营性收费-冻结
     * @param businessChagresId
     */
    @Transactional(rollbackFor = Exception.class)
    public void freeze(Long businessChagresId) {
        PropertyBusinessCharges po = this.getById(businessChagresId);
        if (po.getStatus() != DepositStatusEnum.CHARGED.getValue()) {
            throw new ServiceException("该笔收费当前状态不支持冻结");
        }
        po.setStatus(DepositStatusEnum.FREEZE.getValue());
        this.updateById(po);

        PropertyBill bill = propertyBillSearchService.getBillByRetraceId(businessChagresId);
        propertyBillService.freezeBill(bill.getId());
    }


    /**
     * 经营性收费-解冻
     * @param businessChagresId
     */
    @Transactional(rollbackFor = Exception.class)
    public void unfreeze(Long businessChagresId) {
        PropertyBusinessCharges po = this.getById(businessChagresId);
        if (po.getStatus() != DepositStatusEnum.FREEZE.getValue()) {
            throw new ServiceException("该笔收费当前状态不是冻结状态，不支持解冻");
        }
        po.setStatus(DepositStatusEnum.CHARGED.getValue());
        this.updateById(po);

        PropertyBill bill = propertyBillSearchService.getBillByRetraceId(businessChagresId);
        propertyBillService.unfreezeBill(bill.getId());
    }

    /**
     * 经营性收费-删除
     * @param businessChagresId
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteBusinessCharges(Long businessChagresId) {
        PropertyBusinessCharges po = this.getById(businessChagresId);
        if (!(po.getStatus() == DepositStatusEnum.CONFIRMED.getValue()
                || po.getStatus() == DepositStatusEnum.REJECTED.getValue()
                || po.getStatus() == DepositStatusEnum.FREEZE.getValue())) {
            throw new ServiceException("该笔收费当前状态不支持删除");
        }

        if (po.getStatus() == DepositStatusEnum.CONFIRMED.getValue()) {
            offlinePaymentRecordService.removeByBusinessId(businessChagresId);
        }

        propertyBusinessChargesDetailService.removeDetail(businessChagresId);
        propertyBusinessChargesBillService.removeChargesBill(businessChagresId);

        this.removeById(businessChagresId);

        PropertyBill bill = propertyBillSearchService.getBillByRetraceId(businessChagresId);
        if (bill != null) {
            if (bill.getBillStatus() == BillStatusEnum.FREEZE.getValue()
                    && bill.getPayStatus() == PayStatusEnum.NO_PAY.getValue()){
                propertyBillService.removeBill(bill.getId()); // 账单删除
            } else {
                throw new ServiceException("该笔账单状态不能删除");
            }
        }

    }

}
