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

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.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.*;
import com.yuchen.saas.park.service.event.property.*;
import com.yuchen.saas.park.service.mapper.property.PropertyPrepaymentMapper;
import com.yuchen.saas.park.service.service.property.bill.PropertyBillSearchService;
import com.yuchen.saas.park.service.service.property.bill.PropertyBillService;
import org.nest.springwrap.core.log.exception.ServiceException;
import org.nest.springwrap.core.secure.utils.AuthUtil;
import org.nest.springwrap.core.tool.utils.Func;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
public class PropertyPrepaymentService extends ServiceImpl<PropertyPrepaymentMapper, PropertyPrepayment> {
    @Resource
    private PropertyPaymentRefundAccountInfoService propertyPaymentRefundAccountInfoService;
    @Resource
    private IPropertyFeeItemService propertyFeeItemService;
    @Resource
    private PropertySearchParkInfoService propertySearchParkInfoService;
    @Resource
    private PropertyBillService propertyBillService;
    @Resource
    private PropertyBillSearchService propertyBillSearchService;
    @Resource
    private ApplicationContext applicationContext;
    @Resource
    private PropertyPrepaymentDetailService propertyPrepaymentDetailService;


    /**
     * 预存款充值，退款
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    public Long savePrepayment(PropertyPrepaymentSaveDto dto) {
        this.verifySave(dto);
        PropertyPrepayment prepayment = BeanUtil.copyProperties(dto,PropertyPrepayment.class);
        PropertyPaymentRefundAccountInfo accountInfo = BeanUtil.copyProperties(dto,PropertyPaymentRefundAccountInfo.class);

        if (dto.getIsRefund() == YesOrNoEnum.NO.getValue()) {
            prepayment.setRecordType(RecordTypeEnum.INCOME.getValue());
            accountInfo.setRecordType(RecordTypeEnum.INCOME.getValue());
        } else {
            // 退款
            prepayment.setRecordType(RecordTypeEnum.EXPEND.getValue());
            accountInfo.setRecordType(RecordTypeEnum.EXPEND.getValue());
            prepayment.setAmtFen(dto.getAmtFen()); // 退款金额
        }

        prepayment.setCreateTime(new Date());
        prepayment.setCreateUser(AuthUtil.getUserId());

        if (dto.getIsRefund() == YesOrNoEnum.YES.getValue()) {
            prepayment.setStatus(YesOrNoEnum.YES.getValue());
            // TODO tucq 退款处理
        } else {
            prepayment.setStatus(YesOrNoEnum.NO.getValue());// 线下审核通过变更为1，线上缴费的话支付成功后变更为1
        }

        this.save(prepayment);

        if ((Func.isEmpty(dto.getIsAppPerson()) || dto.getIsAppPerson() == YesOrNoEnum.NO.getValue())) {
            // 线下退款 或 线下充值才需要记录
            accountInfo.setRetraceId(prepayment.getId());
            accountInfo.setAccountBizType(AccountBizTypeEnum.PREPAYMENT.getValue());
            accountInfo.setCreateTime(new Date());
            accountInfo.setCreateUser(AuthUtil.getUserId());
            propertyPaymentRefundAccountInfoService.save(accountInfo);
        }

        if (dto.getIsRefund() == YesOrNoEnum.NO.getValue() // 预存充值
            && (Func.isEmpty(dto.getIsAppPerson()) || dto.getIsAppPerson() == YesOrNoEnum.NO.getValue())) { // 线下充值
            PropertyOfflinePaymentRecordDto recordDto = BeanUtil.copyProperties(accountInfo, PropertyOfflinePaymentRecordDto.class);
            recordDto.setRetraceId(prepayment.getId());
            recordDto.setParkId(prepayment.getParkId());
            recordDto.setHouseId(prepayment.getHouseId());
            recordDto.setCustomerId(prepayment.getCustomerId());
            recordDto.setReceivableAmtFen(prepayment.getAmtFen());
            recordDto.setPayAmtFen(prepayment.getAmtFen());
            recordDto.setRemark(prepayment.getRemark());
            applicationContext.publishEvent(new NotifyOfflinePaymentEvent(recordDto));
        }

        return prepayment.getId();
    }


    /**
     * 业务校验
     * @param dto
     */
    private void verifySave(PropertyPrepaymentSaveDto dto) {
        Long feeItemId = dto.getFeeItemId();
        PropertyFeeItem feeItem = propertyFeeItemService.getById(feeItemId);
        if (feeItem.getEnableStatus() == YesOrNoEnum.NO.getValue()
            && feeItem.getIsDeleted() == YesOrNoEnum.YES.getValue()) {
            throw new ServiceException("该预存款账户状态不可用");
        }
        if (!feeItem.getFeeSubject().equals(FeeItemTypeEnum.PREPAID_DEPOSIT.getValue())) {
            throw new ServiceException("请选择预存款账户");
        }
        if (dto.getIsRefund() == YesOrNoEnum.YES.getValue()) { // 预存款退款
            Long balance = this.getPrepaymentBalanceObj(dto.getParkId(),dto.getCustomerId(),dto.getHouseId(),dto.getFeeItemId());
            if (balance == null) {
                throw new ServiceException("未找到预存款账户");
            }
            if (!balance.equals(dto.getAmtFen())) {
                throw new ServiceException("预存款账户余额有变动，余额="+ AmtUtil.fenToYuan(balance) + "请刷新再确认退款");
            }
        } else {
            // 预存款充值
            // TODO tucq 判断充值金额是否满足收费项目限制
        }

    }


    /**
     * 获取余额 Long 对象
     */
    public Long getPrepaymentBalanceObj(long parkId,long customerId,Long houseId,long feeItemId) {
        PropertyPrepaymentQryDto qryDto = new PropertyPrepaymentQryDto();
        qryDto.setCurrent(1);
        qryDto.setSize(1);
        qryDto.setParkId(parkId);
        qryDto.setCustomerId(customerId);
        qryDto.setHouseId(houseId == null ? 0L : houseId);
        qryDto.setFeeItemId(feeItemId);
        List<PropertyPrepaymentBalanceVo> result = this.selectPage(qryDto).getRecords();
        if (!result.isEmpty()) {
           return result.get(0).getBalanceAmtFen();
        }
        return null;
    }

    /**
     * 获取余额 long 数值
     */
    public long getPrepaymentBalanceLong(long parkId,long customerId,Long houseId,long feeItemId) {
        Long balance = this.getPrepaymentBalanceObj(parkId,customerId,houseId,feeItemId);
        if (balance == null) {
            return 0L;
        }
        return balance;
    }



    /**
     * 预存款余额查询
     *
     * @param qryDto
     * @return
     */
    public Page<PropertyPrepaymentBalanceVo> selectPage(PropertyPrepaymentQryDto qryDto) {
        Page<PropertyPrepaymentBalanceVo> page = new Page<>(qryDto.getCurrent(), qryDto.getSize());
        this.baseMapper.selectBalancePage(page, this.getPageQueryParam(qryDto));
        return this.decoratePage(page);
    }

    private Page<PropertyPrepaymentBalanceVo> decoratePage(Page<PropertyPrepaymentBalanceVo> page) {
        if (page.getRecords().isEmpty()) {
            return page;
        }
        List<Long> houseIdList = page.getRecords().stream().map(PropertyPrepaymentBalanceVo::getHouseId).filter(Objects::nonNull).collect(Collectors.toList());
        List<Long> parkIdList = page.getRecords().stream().map(PropertyPrepaymentBalanceVo::getParkId).filter(Objects::nonNull).collect(Collectors.toList());
        List<Long> customerIdList = page.getRecords().stream().map(PropertyPrepaymentBalanceVo::getCustomerId).collect(Collectors.toList());
        List<Long> feeItemIdList = page.getRecords().stream().map(PropertyPrepaymentBalanceVo::getFeeItemId).collect(Collectors.toList());
        List<PropertyPrepaymentFeeItemInfoVo> feeItemInfoVoList = propertyFeeItemService.listPrepaymentFeeItemInfo(feeItemIdList);

        applicationContext.publishEvent(new SetHouseNameResourceCodeEvent(page.getRecords(), houseIdList));
        applicationContext.publishEvent(new SetParkNameEvent(page.getRecords(),parkIdList));
        applicationContext.publishEvent(new SetCustomerInfoEvent(page.getRecords(),customerIdList));

        page.getRecords().forEach(record-> {
            feeItemInfoVoList.forEach(item -> {
                if (record.getFeeItemId().equals(item.getFeeItemId())) {
                    record.setFeeItemName(item.getFeeItemName());
                    record.setFeeNameStr(item.getBalanceType() == 1 ? "所有项目" : item.getFeeNameStr());
                }
            });
        });

        return page;
    }

    private QueryWrapper<String> getPageQueryParam(PropertyPrepaymentQryDto qryDto) {
        QueryWrapper<String> qw = new QueryWrapper<>();
        if (Func.isNotEmpty(qryDto.getParkId())) {
            qw.eq("p1.park_id",qryDto.getParkId());
        } else {
            qw.in("p1.park_id", propertySearchParkInfoService.getParkIdsByCurrUser());
        }
        if (Func.isNotEmpty(qryDto.getCustomerId())) {
            qw.eq("p1.customer_id", qryDto.getCustomerId());
        }
        if (Func.isNotEmpty(qryDto.getFeeItemId())) {
            qw.eq("p1.fee_item_id", qryDto.getFeeItemId());
        }
        if (Func.isNotEmpty(qryDto.getHouseId())) {
            qw.eq("p1.house_id", qryDto.getHouseId());
        }
        qw.orderByAsc("p1.fee_item_id");
        return qw;
    }

    /**
     * 抵扣欠费详情
     * @param deductDto
     * @return
     */
    public PropertyPrepaymentDeductVo deductDetail(PropertyPrepaymentDeductDto deductDto) {
        PropertyPrepaymentDeductVo vo = new PropertyPrepaymentDeductVo();
        Long totalBalanceAmtFen = this.getPrepaymentBalanceObj(deductDto.getParkId(),deductDto.getCustomerId(),deductDto.getHouseId(),deductDto.getFeeItemId());
        if (totalBalanceAmtFen == null) {
            throw new ServiceException("未找到预存款账户");
        }
        vo.setTotalBalanceAmtFen(totalBalanceAmtFen);
        vo.setArrearsAmtFen(0L);
        PropertyPrepaymentDeductViewDto viewDto = this.getDeductViewDto(deductDto,totalBalanceAmtFen);
        List<PropertyBillListVo> billList = viewDto.getBillList();
        if (Func.isEmpty(billList.isEmpty())) {
            return vo;
        }
        List<Long> houseIdList = billList.stream().map(PropertyBillListVo::getHouseId).collect(Collectors.toList());
        List<Long> feeIdList = billList.stream().map(PropertyBillListVo::getFeeId).collect(Collectors.toList());

        applicationContext.publishEvent(new SetHouseNameResourceCodeEvent(viewDto.getDeductListVoList(),houseIdList));
        applicationContext.publishEvent(new SetFeeNameEvent(viewDto.getDeductListVoList(),feeIdList));

        vo.setDeductListVoList(viewDto.getDeductListVoList());
        vo.setArrearsAmtFen(viewDto.getArrearsAmtFen());
        vo.setDeductAmtFen(viewDto.getDeductAmtFen());
        return vo;
    }

    private PropertyPrepaymentDeductViewDto getDeductViewDto(PropertyPrepaymentDeductDto deductDto, long totalBalanceAmtFen) {
        PropertyPrepaymentDeductViewDto viewDto = new PropertyPrepaymentDeductViewDto();
        PropertyBillPageQryDto qryDto = BeanUtil.copyProperties(deductDto,PropertyBillPageQryDto.class);
        qryDto.setCurrent(-1);
        qryDto.setSize(-1);
        qryDto.setBillStatus(BillStatusEnum.YES_OUT.getValue());
        qryDto.setPayStatusList(PayStatusEnum.getNeedPayStatus());
        qryDto.setIsOrderByAsc(YesOrNoEnum.YES.getValue());
        qryDto.setOrderByColumn("receivable_date");
        if (Func.isNotEmpty(deductDto.getBillIdList())) { // 收银台-预存款抵扣实可能会传该参数
            qryDto.setBillIdList(deductDto.getBillIdList());
        }
        PropertyFeeItem feeItem = propertyFeeItemService.getById(deductDto.getFeeItemId());
        if (feeItem.getBalanceType() == 2) { // 费用项目指定收费标准
            qryDto.setFeeIdList(propertyFeeItemService.getFeeItemBindFeeIdList(deductDto.getFeeItemId()));
        }
        qryDto.setFeeItemId(null); // 注意：查询账单不能带入预存款费用项目id，因为收费标准不会挂在预存款费用项目下的
        List<PropertyBillListVo> billList = propertyBillSearchService.selectBillFullInfoPage(qryDto).getRecords();
        if (Func.isEmpty(billList)) {
            return viewDto;
        }
        List<PropertyPrepaymentDeductListVo> deductListVoList = BeanUtil.copyToList(billList,PropertyPrepaymentDeductListVo.class);
        long arrearsAmtFen = 0L;
        long deductAmtFen = 0L;
        for (PropertyPrepaymentDeductListVo item: deductListVoList) {
            if (totalBalanceAmtFen <= item.getNoPayAmtFen()) {
                item.setDeductAmtFen(totalBalanceAmtFen);
                totalBalanceAmtFen = 0L;
            } else {
                item.setDeductAmtFen(item.getNoPayAmtFen());
                totalBalanceAmtFen = totalBalanceAmtFen - item.getNoPayAmtFen();
            }
            arrearsAmtFen += item.getNoPayAmtFen();
            deductAmtFen += item.getDeductAmtFen();

            item.setDeductBalanceAmtFen(totalBalanceAmtFen);
        }

        viewDto.setBillList(billList);
        viewDto.setDeductListVoList(deductListVoList);
        viewDto.setArrearsAmtFen(arrearsAmtFen);
        viewDto.setDeductAmtFen(deductAmtFen);
        return viewDto;
    }


    /**
     * 预存款抵扣
     * @param deductDto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public void deduct(PropertyPrepaymentDeductDto deductDto) {
        if (Func.isEmpty(deductDto.getDeductAmt())) {
            throw new ServiceException("收费总计不能为空");
        }
        if (Func.isEmpty(deductDto.getRecordTime())) {
            throw new ServiceException("收款时间不能为空");
        }
        long balance = this.getPrepaymentBalanceLong(deductDto.getParkId(), deductDto.getCustomerId(), deductDto.getHouseId(),deductDto.getFeeItemId());
        if (balance < AmtUtil.yuanToFen(deductDto.getDeductAmt())) {
            throw new ServiceException("当前预存款额度发生变化，总可用余额为：" + AmtUtil.fenToYuan(balance) + "，请刷新后重新操作");
        }
        PropertyPrepaymentDeductViewDto viewDto = this.getDeductViewDto(deductDto,balance);
        if (Func.isEmpty(viewDto.getBillList())) {
            throw new ServiceException("未找到预存款对应的欠费账单记录");
        }

        PropertyPrepayment prepayment = BeanUtil.copyProperties(deductDto,PropertyPrepayment.class);
        prepayment.setAmtFen(viewDto.getDeductAmtFen());
        prepayment.setRecordType(RecordTypeEnum.EXPEND.getValue());
        prepayment.setIsRefund(YesOrNoEnum.NO.getValue());
        prepayment.setCreateUser(AuthUtil.getUserId());
        prepayment.setCreateTime(new Date());
        this.save(prepayment);

        List<PropertyPrepaymentDetail> detailList = new ArrayList<>();
        List<PropertyPrepaymentDeductListVo> deductListVoList = viewDto.getDeductListVoList();
        deductListVoList.forEach(deductVo -> {
            if (deductVo.getDeductAmtFen() > 0) {
                PropertyPrepaymentDetail detail = new PropertyPrepaymentDetail();
                detail.setPrepaymentId(prepayment.getId());
                detail.setBillId(deductVo.getId());
                detail.setNeedPayAmtFen(deductVo.getNoPayAmtFen());
                detail.setDeductAmtFen(deductVo.getDeductAmtFen());
                detail.setCreateTime(new Date());
                detailList.add(detail);
                propertyBillService.pay(deductVo.getId(),deductVo.getDeductAmtFen());
            }
        });

        propertyPrepaymentDetailService.saveBatch(detailList);
    }


    /**
     * 预存款收支明细
     * @param qryDto
     * @return
     */
    public Page<PropertyPrepaymentListVo> prepaymentList(PropertyPrepaymentQryDto qryDto) {
        Page<PropertyPrepaymentListVo> page = new Page<>(qryDto.getCurrent(), qryDto.getSize());
        QueryWrapper<String> qw = new QueryWrapper<>();
        qw.eq("p.park_id",qryDto.getParkId());
        qw.eq("p.customer_id",qryDto.getCustomerId());
        qw.eq("p.fee_item_id",qryDto.getFeeItemId());
        if (Func.isNotEmpty(qryDto.getHouseId())) {
            qw.eq("p.house_id",qryDto.getHouseId());
        }
        qw.orderByDesc("p.record_time");
        this.baseMapper.selectPrepaymentPage(page,qw);

        List<Long> customerIdList = page.getRecords().stream().map(PropertyPrepaymentListVo::getCustomerId).collect(Collectors.toList());
        applicationContext.publishEvent(new SetCustomerInfoEvent(page.getRecords(),customerIdList));

        return page;
    }

    /**
     * 预存款明细（存入，支出，退款）
     * @param qryDto
     * @return
     */
    public Page<PropertyPrepaymentDetailListVo> prepaymentDetailList(PropertyPrepaymentQryDto qryDto) {
        Page<PropertyPrepaymentDetailListVo> page = new Page<>(qryDto.getCurrent(), qryDto.getSize());
        QueryWrapper<String> qw = new QueryWrapper<>();
        if (Func.isNotEmpty(qryDto.getPrepaymentId())) {
            qw.eq("pd.prepayment_id",qryDto.getPrepaymentId());
        } else {
            qw.eq("p.park_id",qryDto.getParkId());
            qw.eq("p.customer_id",qryDto.getCustomerId());
            qw.eq("p.house_id",qryDto.getHouseId());
            qw.eq("p.fee_item_id",qryDto.getFeeItemId());
        }
        if (Func.isNotEmpty(qryDto.getStartRecordTime())) {
            qw.le("p.record_time",qryDto.getStartRecordTime());
        }
        if (Func.isNotEmpty(qryDto.getEndRecordTime())) {
            qw.ge("p.record_time",qryDto.getEndRecordTime());
        }
        qw.orderByDesc("p.record_time");
        this.baseMapper.prepaymentDetailList(page,qw);
        return page;
    }



    public List<PropertyPrepaymentFeeItemListVo> listPrepaymentAccount(PropertyPrepaymentFeeItemBalanceDto dto) {
        LambdaQueryWrapper<PropertyFeeItem> qw = new LambdaQueryWrapper<>();
        qw.eq(PropertyFeeItem::getEnableStatus,YesOrNoEnum.YES.getValue());
        qw.eq(PropertyFeeItem::getFeeSubject,FeeItemTypeEnum.PREPAID_DEPOSIT.getValue());
        qw.eq(PropertyFeeItem::getParkId,dto.getParkId());
        List<PropertyFeeItem> feeItemList = propertyFeeItemService.list(qw);
        List<PropertyPrepaymentFeeItemListVo> voList = new ArrayList<>();
        for (PropertyFeeItem item : feeItemList) {
            PropertyPrepaymentFeeItemListVo vo = new PropertyPrepaymentFeeItemListVo();
            vo.setFeeItemId(item.getId());
            vo.setFeeItemName(item.getFeeItemName());
            Long balanceAmtFen = this.getPrepaymentBalanceObj(dto.getParkId(),dto.getCustomerId(),dto.getHouseId(),item.getId());
            vo.setBalanceAmtFen(balanceAmtFen == null ? 0L : balanceAmtFen);
            voList.add(vo);
        }
        return voList;
    }


}
