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.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.NotifyOfflinePaymentEvent;
import com.yuchen.saas.park.service.event.property.SetCreateUserNameEvent;
import com.yuchen.saas.park.service.event.property.SetCustomerInfoEvent;
import com.yuchen.saas.park.service.mapper.property.PropertyDepositMapper;
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 lombok.extern.slf4j.Slf4j;
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 javax.validation.groups.Default;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 押金管理
 */
@Slf4j
@Service
public class PropertyDepositService extends ServiceImpl<PropertyDepositMapper, PropertyDeposit> {
    @Resource
    private ManualValidUtil manualValidUtil;
    @Resource
    private PropertyDepositHouseService propertyDepositHouseService;
    @Resource
    private PropertyBillFactoryService propertyBillFactoryService;
    @Resource
    private PropertySearchParkInfoService propertySearchParkInfoService;
    @Resource
    private IPropertyOfflinePaymentRecordService offlinePaymentRecordService;
    @Resource
    private PropertyFeeStandardService propertyFeeStandardService;
    @Resource
    private PropertyBillService propertyBillService;
    @Resource
    private PropertyBillSearchService propertyBillSearchService;
    @Resource
    private IPropertyFeeItemService propertyFeeItemService;
    @Resource
    private PropertyRefundInfoService propertyRefundInfoService;
    @Resource
    private ApplicationContext applicationContext;


    /**
     * 保存
     * @param depositSaveDto
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveDeposit(PropertyDepositSaveDto depositSaveDto) {
        this.validParam(depositSaveDto);
        this.verifySave(depositSaveDto);
        PropertyDeposit deposit = this.assembleDeposit(depositSaveDto);
        this.saveOrUpdate(deposit);

        if (depositSaveDto.getIsRefund() == YesOrNoEnum.NO.getValue()) { // 交押金
            LambdaQueryWrapper<PropertyDepositHouse> qw = new LambdaQueryWrapper<>();
            qw.eq(PropertyDepositHouse::getDepositId,deposit.getId());
            propertyDepositHouseService.remove(qw);

            if (Func.isNotEmpty(depositSaveDto.getHouseIdList())) {
                List<PropertyDepositHouse> list = new ArrayList<>();
                depositSaveDto.getHouseIdList().forEach(houseId -> {
                    PropertyDepositHouse depositHouse = new PropertyDepositHouse();
                    depositHouse.setDepositId(deposit.getId());
                    depositHouse.setHouseId(houseId);
                    depositHouse.setCreateTime(new Date());
                    list.add(depositHouse);
                });
                propertyDepositHouseService.saveBatch(list);
            }

            if (deposit.getDepositStatus() == DepositStatusEnum.CHARGED.getValue()) {
                CreateBillDto createBillDto = CreateBillDto.create();
                PropertyDepositCreateBillDto depositCreateBillDto = new PropertyDepositCreateBillDto();
                BeanUtil.copyProperties(deposit,depositCreateBillDto);
                depositCreateBillDto.setDepositId(deposit.getId());
                if (Func.isNotEmpty(depositSaveDto.getHouseIdList()) && depositSaveDto.getHouseIdList().size() == 1) {
                    // 如果一笔押金交的是多个房间的，那这个字段为空
                    depositCreateBillDto.setHouseId(depositSaveDto.getHouseIdList().get(0));
                }
                createBillDto.setDepositCreateBillDto(depositCreateBillDto);
                createBillDto.setCreateBillTypeEnum(CreateBillTypeEnum.DEPOSIT);
                propertyBillFactoryService.createBill(createBillDto);
            } else if (deposit.getDepositStatus() == DepositStatusEnum.CONFIRMED.getValue()) {
                // 线下收费审核单
                PropertyOfflinePaymentRecordDto recordDto = BeanUtil.copyProperties(deposit, PropertyOfflinePaymentRecordDto.class);
                recordDto.setRetraceId(deposit.getId());
                if (Func.isNotEmpty(depositSaveDto.getHouseIdList()) && depositSaveDto.getHouseIdList().size() == 1) {
                    recordDto.setHouseId(depositSaveDto.getHouseIdList().get(0));
                }
                recordDto.setReceivableAmtFen(deposit.getDepositAmtFen());
                recordDto.setPayAmtFen(deposit.getDepositAmtFen());
                recordDto.setAccountBizType(AccountBizTypeEnum.DEPOSIT.getValue());
                recordDto.setRecordTime(deposit.getPayTime());
                applicationContext.publishEvent(new NotifyOfflinePaymentEvent(recordDto));
            }
        }

    }

    private PropertyDeposit assembleDeposit(PropertyDepositSaveDto dto) {
        PropertyDeposit deposit = this.getById(dto.getId());
        if (Func.isNotEmpty(deposit)) { // 编辑
            BeanUtil.copyProperties(dto,deposit);
            deposit.setUpdateTime(new Date());
            deposit.setUpdateUser(AuthUtil.getUserId());
        } else {
            deposit = new PropertyDeposit();
            BeanUtil.copyProperties(dto,deposit);
            if (dto.getChargeType() == YesOrNoEnum.NO.getValue()) {
                deposit.setDepositStatus(DepositStatusEnum.CHARGED.getValue());
            } else {
                deposit.setDepositStatus(DepositStatusEnum.CONFIRMED.getValue());
            }
            deposit.setCreateTime(new Date());
            deposit.setCreateUser(AuthUtil.getUserId());
        }

        return deposit;
    }

    /**
     * 业务校验
     * @param depositSaveDto
     */
    private void verifySave(PropertyDepositSaveDto depositSaveDto) {
        PropertyDeposit deposit = this.getById(depositSaveDto.getId());
        if (Func.isNotEmpty(depositSaveDto.getId()) &&
                !(deposit.getDepositStatus() == DepositStatusEnum.CONFIRMED.getValue()
                || deposit.getDepositStatus() == DepositStatusEnum.FREEZE.getValue())) {
            throw new ServiceException("当前押金状态不支持编辑");
        }

        Long feeId = depositSaveDto.getFeeId();
        PropertyFeeStandard fee = propertyFeeStandardService.getById(feeId);
        PropertyFeeItem feeItem = propertyFeeItemService.getById(fee.getFeeItemId());
        if (feeItem == null) {
            throw new ServiceException("该费用项目状态已被删除，请联系技术人员");
        }
        if (!feeItem.getFeeSubject().equals(FeeItemTypeEnum.SECURITY_DEPOSIT.getValue())) {
            throw new ServiceException("请选择保证金相关收费标");
        }

    }

    /**
     * 参数校验
     *
     * @param depositSaveDto
     */
    private void validParam(PropertyDepositSaveDto depositSaveDto) {
        List<Class> groupList = CollUtil.newArrayList(Default.class);
        if (depositSaveDto.getIsRefund() == YesOrNoEnum.NO.getValue()) { // 交押金
            if (Func.isEmpty(depositSaveDto.getChargeType())) {
                throw new ServiceException("收费状态不能为空");
            }
            if (depositSaveDto.getChargeType() == YesOrNoEnum.NO.getValue()) {
                groupList.add(PropertyDepositSaveDto.Unpaid.class);
            }
            if (depositSaveDto.getChargeType() == YesOrNoEnum.YES.getValue()
                    && Func.isEmpty(depositSaveDto.getPayType())) {
                throw new ServiceException("收费方式不能为空");
            }
            if (Func.isNotEmpty(depositSaveDto.getPayType())
                    && depositSaveDto.getChargeType() == YesOrNoEnum.YES.getValue()) {
                switch (depositSaveDto.getPayType()) {
                    case 1:
                        groupList.add(PropertyDepositSaveDto.Cash.class);
                        break;
                    case 2:
                        groupList.add(PropertyDepositSaveDto.Third.class);
                        break;
                    case 3:
                        groupList.add(PropertyDepositSaveDto.Bank.class);
                        break;
                }
            }
        } else { // 退款
            if (Func.isEmpty(depositSaveDto.getRefundType())) {
                throw new ServiceException("退款方式不能为空");
            }
            switch (depositSaveDto.getRefundType()) {
                case 1:
                    groupList.add(PropertyDepositSaveDto.RefundRoad.class);
                    break;
                case 2:
                    groupList.add(PropertyDepositSaveDto.RefundThird.class);
                    break;
                case 3:
                    groupList.add(PropertyDepositSaveDto.RefundBank.class);
                    break;
            }
        }

        manualValidUtil.valid(depositSaveDto, groupList);
    }

    /**
     * 根据房源查询押金余额
     *
     * @param houseId
     * @return
     */
    public long getDepositBalanceFen(long houseId) {
        List<PropertyDepositHouseVo> resList = this.listHouseDeposit(houseId);
        if (Func.isEmpty(resList)) {
            return 0;
        }
        long totalDepositAmtFen = 0; // 押金总和
        long totalRefundAmtFen = 0; // 退款总和
        for (PropertyDepositHouseVo vo : resList) {
            totalDepositAmtFen += vo.getDepositAmtFen();
            totalRefundAmtFen += vo.getRefundAmtFen();
        }
        long balanceAmtFen = totalDepositAmtFen - totalRefundAmtFen;
        if (balanceAmtFen < 0) {
            log.info("getDepositBalanceFenHouseId = " + houseId);
            throw new ServiceException("查询房源押金余额异常");
        }
        return balanceAmtFen ;
    }

    /**
     * 根据房源id查询房源押金和退款
     * @param houseId
     * @return
     */
    public List<PropertyDepositHouseVo> listHouseDeposit(long houseId) {
        return this.baseMapper.listHouseDeposit(houseId);
    }


    /**
     * 押金列表查询
     *
     * @param qryDto
     * @return
     */
    public Page<PropertyDepositListVo> selectPage(PropertyDepositQryDto qryDto) {
        Page<PropertyDeposit> page = new Page<>(qryDto.getCurrent(), qryDto.getSize());
        this.page(page, this.getPageQueryParam(qryDto));
        return this.decoratePage(page,qryDto);
    }

    private Page<PropertyDepositListVo> decoratePage(Page<PropertyDeposit> depositPage,PropertyDepositQryDto qryDto) {
        Page<PropertyDepositListVo> page = new Page<>(qryDto.getCurrent(), qryDto.getSize());
        if (depositPage.getRecords().size() == 0) {
            return page;
        }
        List<PropertyDepositListVo> records = BeanUtil.copyToList(depositPage.getRecords(),PropertyDepositListVo.class);

        List<Long> userIds = records.stream().map(PropertyDepositListVo::getCreateUser).collect(Collectors.toList());

        List<Long> feeIds = records.stream().map(PropertyDepositListVo::getFeeId).collect(Collectors.toList());
        PropertyFeeStandardQryDto feeQry = new PropertyFeeStandardQryDto();
        feeQry.setFeeIdList(feeIds);
        List<PropertyFeeStandardListVo> feeVoList = propertyFeeStandardService.listPageVo(feeQry);

        List<Long> depositIdList = records.stream().map(PropertyDepositListVo::getId).collect(Collectors.toList());
        List<PropertyDepositHouseGroupListVo> depHouseList = propertyDepositHouseService.listDepositHouseGroupVo(depositIdList);

        List<Long> customerIdList = records.stream().map(PropertyDepositListVo::getCustomerId).collect(Collectors.toList());

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

        records.forEach(record -> {
            feeVoList.forEach(feeVo -> {
                if (record.getFeeId().equals(feeVo.getId())) {
                    record.setFeeName(feeVo.getFeeName());
                    record.setParkName(feeVo.getParkName());
                    record.setFeeItemName(feeVo.getFeeItemName());
                }
            });
            depHouseList.forEach(item -> {
                if (record.getId().equals(item.getDepositId())) {
                    record.setHouseName(item.getHouseName());
                }
            });
            // TODO tucq 付款类型，付款方式
        });

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

    }

    private LambdaQueryWrapper<PropertyDeposit> getPageQueryParam(PropertyDepositQryDto qryDto) {
        LambdaQueryWrapper<PropertyDeposit> qw = new LambdaQueryWrapper<>();
        if (Func.isNotEmpty(qryDto.getParkId())) {
            qw.eq(PropertyDeposit::getParkId,qryDto.getParkId());
        } else {
            qw.in(PropertyDeposit::getParkId, propertySearchParkInfoService.getParkIdsByCurrUser());
        }
        if (Func.isNotEmpty(qryDto.getCustomerId())) {
            qw.eq(PropertyDeposit::getCustomerId, qryDto.getCustomerId());
        }
        if (Func.isNotEmpty(qryDto.getFeeId())) {
            qw.eq(PropertyDeposit::getFeeId, qryDto.getFeeId());
        }
        if (Func.isNotEmpty(qryDto.getDepositStatus())) {
            if (qryDto.getDepositStatus() == DepositStatusEnum.ARREARS.getValue()) {
                //欠费 TODO tucq
            } else {
                qw.eq(PropertyDeposit::getDepositStatus, qryDto.getDepositStatus());
            }
        }
        if (Func.isNotEmpty(qryDto.getHouseId())) {
            List<Long> depositIdList = propertyDepositHouseService.listDepositIdByHouseId(qryDto.getHouseId());
            if (Func.isEmpty(depositIdList)) {
                qw.in(PropertyDeposit::getId,"-1");
            } else {
                qw.in(PropertyDeposit::getId,depositIdList);
            }
        }
        if (Func.isNotEmpty(qryDto.getFeeItemId())) { // 费用项目
            List<PropertyFeeStandard> feeList = propertyFeeStandardService.listFeeByFeeItemId(qryDto.getFeeItemId());
            if (Func.isNotEmpty(feeList)) {
                qw.in(PropertyDeposit::getFeeId,feeList.stream().map(PropertyFeeStandard::getId).collect(Collectors.toList()));
            }
        }
        if (Func.isNotEmpty(qryDto.getStartDate())) {
            qw.le(PropertyDeposit::getStartDate, qryDto.getStartDate());
        }
        if (Func.isNotEmpty(qryDto.getEndDate())) {
            qw.ge(PropertyDeposit::getEndDate, qryDto.getEndDate());
        }
        if (Func.isNotEmpty(qryDto.getStartCreateTime())) {
            qw.le(PropertyDeposit::getCreateTime, qryDto.getStartCreateTime());
        }
        if (Func.isNotEmpty(qryDto.getEndCreateTime())) {
            qw.ge(PropertyDeposit::getCreateTime, qryDto.getEndCreateTime());
        }
        qw.orderByDesc(PropertyDeposit::getCreateTime);
        return qw;
    }

    /**
     * 详情
     * @param depositId
     */
    public PropertyDepositDetailVo detail(Long depositId) {
        PropertyDeposit po = this.getById(depositId);
        PropertyDepositDetailVo vo = BeanUtil.copyProperties(po, PropertyDepositDetailVo.class);

        List<Long> depositIdList = new ArrayList<>();
        depositIdList.add(depositId);
        List<PropertyDepositHouseListVo> houseListVos = propertyDepositHouseService.listDepositHouseVo(depositIdList);

        vo.setHouseInfoList(houseListVos);
        vo.setRefundInfo(propertyRefundInfoService.getInfoByRetraceId(depositId));

        return vo;
    }


    /**
     * 押金冻结
     * @param depositId
     */
    @Transactional(rollbackFor = Exception.class)
    public void freezeDeposit(Long depositId) {
        PropertyDeposit po = this.getById(depositId);
        if (po.getDepositStatus() != DepositStatusEnum.CHARGED.getValue()) {
            throw new ServiceException("该笔押金当前状态不支持冻结");
        }
        po.setDepositStatus(DepositStatusEnum.FREEZE.getValue());
        this.updateById(po);

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


    /**
     * 押金解冻
     * @param depositId
     */
    @Transactional(rollbackFor = Exception.class)
    public void unfreezeDeposit(Long depositId) {
        PropertyDeposit po = this.getById(depositId);
        if (po.getDepositStatus() != DepositStatusEnum.FREEZE.getValue()) {
            throw new ServiceException("该笔押金当前状态不是冻结状态，不支持解冻");
        }
        po.setDepositStatus(DepositStatusEnum.CHARGED.getValue());
        this.updateById(po);

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



    /**
     * 押金删除
     * @param depositId
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteDeposit(Long depositId) {
        PropertyDeposit po = this.getById(depositId);
        if (!(po.getDepositStatus() == DepositStatusEnum.CONFIRMED.getValue()
                || po.getDepositStatus() == DepositStatusEnum.REJECTED.getValue()
                || po.getDepositStatus() == DepositStatusEnum.FREEZE.getValue())) {
            throw new ServiceException("该笔押金当前状态不支持删除");
        }

        if (po.getDepositStatus() == DepositStatusEnum.CONFIRMED.getValue()) {
            offlinePaymentRecordService.removeByBusinessId(depositId);
        }

        LambdaQueryWrapper<PropertyDepositHouse> qw = new LambdaQueryWrapper<>();
        qw.eq(PropertyDepositHouse::getDepositId,po.getId());
        propertyDepositHouseService.remove(qw); // 押金绑定房源明细

        this.removeById(depositId);

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

    }


    /**
     * 押金退款
     * @param depositSaveDto
     */
    @Transactional(rollbackFor = Exception.class)
    public void refund(PropertyDepositSaveDto depositSaveDto) {
        this.validParam(depositSaveDto);
        PropertyDeposit po = this.getById(depositSaveDto.getId());
        PropertyBill bill = propertyBillSearchService.getBillByRetraceId(depositSaveDto.getId());
        if (!(po.getDepositStatus() == DepositStatusEnum.PAID.getValue()
                && bill.getPayStatus() == PayStatusEnum.PAID.getValue())) {
            throw new ServiceException("该笔押金当前状态不支持退款操作");
        }
        long refundAmtFen;
        if (depositSaveDto.getRefundType() == 1) {
            refundAmtFen = po.getDepositAmtFen(); // 在线原路退回，押金金额=退款金额
        } else {
            if (depositSaveDto.getRefundAmtFen() > po.getDepositAmtFen()) {
                throw new ServiceException("退款金额不能大于押金金额");
            }
            refundAmtFen = depositSaveDto.getRefundAmtFen();
        }

        po.setIsRefund(YesOrNoEnum.YES.getValue());
        po.setRefundAmtFen(refundAmtFen);
        po.setRefundTime(new Date());
        this.updateById(po);

        PropertyRefundInfo refundInfo = BeanUtil.copyProperties(depositSaveDto,PropertyRefundInfo.class);
        refundInfo.setId(null);
        refundInfo.setRetraceId(po.getId());
        refundInfo.setRefundBizType(RefundBizTypeEnum.DEPOSIT.getValue());
        refundInfo.setRefundAmtFen(refundAmtFen);
        refundInfo.setCreateTime(new Date());
        refundInfo.setCreateUser(AuthUtil.getUserId());
        propertyRefundInfoService.save(refundInfo);

        propertyBillService.refund(bill.getId(),refundAmtFen);

    }




}
