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

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yuchen.saas.park.api.dto.property.CreateBillDto;
import com.yuchen.saas.park.api.dto.property.PropertyCycleCreateBillDto;
import com.yuchen.saas.park.api.entity.property.*;
import com.yuchen.saas.park.api.enums.property.*;
import com.yuchen.saas.park.api.vo.property.PropertyBillMonthDay;
import com.yuchen.saas.park.service.constant.PropertyCacheConstant;
import com.yuchen.saas.park.service.mapper.property.PropertyBillMapper;
import com.yuchen.saas.park.service.service.property.*;
import com.yuchen.saas.parking.api.util.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.nest.springwrap.core.log.exception.ServiceException;
import org.nest.springwrap.core.redis.cache.NestRedis;
import org.nest.springwrap.core.tool.constant.NestConstant;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;

/**
 *  TODO tucq 预收
 */
@Slf4j
@Service
public class BeforehandBillServiceImpl extends BasePropertyBillService implements IPropertyBillService {
    @Resource
    private PropertyBillMapper billMapper;
    @Resource
    private PropertyFeeStandardService propertyFeeStandardService;
    @Resource
    private IPropertyHouseBindFeeService propertyHouseBindFeeService;
    @Resource
    private PropertyBillSubService propertyBillSubService;
    @Resource
    private PropertyHousesService propertyHousesService;
    @Resource
    private IPropertyHouseResidentService propertyHouseResidentService;
    @Resource
    private NestRedis nestRedis;
    @Resource
    private BeanFactory beanFactory;

    private BeforehandBillServiceImpl thisService() {
        return beanFactory.getBean(BeforehandBillServiceImpl.class);
    }


    /**
     * 周期性账单生成
     *
     * @param createBillDto
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createBill(CreateBillDto createBillDto) {
        PropertyCycleCreateBillDto cycleDto = createBillDto.getCycleCreateBillDto();
        super.validParam(createBillDto);
        PropertyFeeStandard fee = propertyFeeStandardService.getById(cycleDto.getFeeId());
        if (!this.verifyBiz(cycleDto,fee)) {
            return;
        }
        PropertyBill bill = this.assembleBill(createBillDto,fee);
        billMapper.insert(bill);
        PropertyBillSub sub = super.assembleBillSub(fee);
        sub.setBillId(bill.getId());
        sub.setCalcCycleMonths(bill.getCalcCycleMonths());
        sub.setCalcCycleDays(bill.getCalcCycleDays());

        propertyBillSubService.save(sub);

    }


    /**
     * 账单信息组装
     *
     * @param createBillDto
     * @return
     */
    private PropertyBill assembleBill(CreateBillDto createBillDto,PropertyFeeStandard fee) {
        PropertyCycleCreateBillDto cycleDto = createBillDto.getCycleCreateBillDto();
        PropertyBill bill = super.assembleBaseBill(fee, createBillDto.getCreateBillTypeEnum());
        Date startDate = this.getStartDate(cycleDto);
        Date endDate = this.getEndDate(cycleDto, fee);
        PropertyBillMonthDay monthDay = this.getBillMonthsAndDays(startDate, endDate, fee, cycleDto);
        long receivableAmt = this.getReceivableAmt(monthDay, fee, cycleDto);
        Date receivableDate = super.getReceivableDate(startDate, endDate, fee);

        bill.setBillStatus(BillStatusEnum.YES_OUT.getValue());

        PropertyHouses houses = propertyHousesService.getById(cycleDto.getHouseId());
        bill.setBuildingId(houses.getBuildingId());
        bill.setHouseId(cycleDto.getHouseId());
        bill.setCustomerId(super.getHouseCustomerId(cycleDto.getHouseId()));
        bill.setPriceFen(fee.getPriceFen());
        bill.setPriceUnitType(fee.getPriceUnitType());
        bill.setStartDate(startDate);
        bill.setEndDate(endDate);
        bill.setCalcCycleMonths(monthDay.getMonths());
        bill.setCalcCycleDays(monthDay.getDays());
        bill.setReceivableAmtFen(super.decimalPoint(fee.getParkId(),receivableAmt));
        bill.setReceivableDate(receivableDate);
        bill.setBreachDate(super.getBreachDate(receivableDate, startDate, endDate, fee));

        bill.setTaxAmtFen(super.calcTaxAmtFen(fee.getFeeItemId(), bill.getReceivableAmtFen()));

        return bill;
    }


    /**
     * 业务校验，校验是否符合账单生成规则
     *
     * @param cycleDto
     */
    private boolean verifyBiz(PropertyCycleCreateBillDto cycleDto,PropertyFeeStandard fee) {
        boolean isFeeItemEnable = super.checkFeeItemStatus(fee.getFeeItemId());
        if (!isFeeItemEnable) { // 校验费用项目是否开启
            return false;
        }

//        Date currDate = new Date();

        // 账单预先生成日期 > 系统当前日期，不生成账单，即未到账单生成时间
//        if (DateUtil.compare(this.getBeforeCreateBillDate(cycleDto,fee), currDate, "yyyy-MM-dd") > 0) {
//            return false;
//        }

        // 账单开始日期 > 缴费计算截止日期，不生成账单
        PropertyHouseBindFee houseBindFee = thisService().getHouseBindFee(cycleDto);
        if (houseBindFee.getEndFeeDate() != null
                && DateUtil.compare(this.getStartDate(cycleDto), houseBindFee.getEndFeeDate(), "yyyy-MM-dd") > 0) {
            return false;
        }

//        // 最后一笔账单截止日期 >= 系统当期日期不生成账单，即本期账单已生成
//        PropertyBill bill = thisService().getLastBill(dto);
//        if (bill != null && DateUtil.compare(bill.getEndDate(), currDate, "yyyy-MM-dd") >= 0) {
//            return false;
//        }


        return true;
    }

    /**
     * 根据收费标准规则计算账单预先生成日期
     *
     * @param cycleDto
     * @return
     */
    private Date getBeforeCreateBillDate(PropertyCycleCreateBillDto cycleDto,PropertyFeeStandard fee) {
        Date nextStartDate = this.getStartDate(cycleDto);
        Date beforeCreateBillDate;// 账单提前生成日期
        if (fee.getBillGenerateUnit() == MonthDayEnum.MONTH.getValue()) { // 提前 X 月生成账单
            beforeCreateBillDate = DateUtil.offsetMonth(nextStartDate, fee.getBillGenerateVal() * -1);
        } else { // 提前 x 日生成账单
            beforeCreateBillDate = DateUtil.offsetDay(nextStartDate, fee.getBillGenerateVal() * -1);
        }
        return beforeCreateBillDate;
    }

    /**
     * 计算账单开始日期
     *
     * @param cycleDto
     * @return
     */
    private Date getStartDate(PropertyCycleCreateBillDto cycleDto) {
        PropertyBill bill = thisService().getLastBill(cycleDto);
        Date nextStartDate; // 下一周期账单起始时间
        if (bill == null) { // 从未生成账单过
            PropertyHouseBindFee houseBindFee = thisService().getHouseBindFee(cycleDto);
            nextStartDate = houseBindFee.getStartFeeDate();
        } else {
            nextStartDate = DateUtil.offsetDay(bill.getEndDate(), 1);
        }
        return nextStartDate;
    }


    /**
     * 计算账单结束日期
     *
     * @param cycleDto
     * @param fee
     * @return
     */
    private Date getEndDate(PropertyCycleCreateBillDto cycleDto, PropertyFeeStandard fee) {
        Date startDate = this.getStartDate(cycleDto);
        Date endDate;
        if (fee.getBillUnit() == MonthDayEnum.MONTH.getValue()) { // 账单周期按月
            if (fee.getBillStage() == BillStageEnum.BY_DAY.getValue()) {
                endDate = DateUtil.offsetMonth(startDate, fee.getBillCycle());
                int day1 = DateUtil.dayOfMonth(startDate);
                int day2 = DateUtil.dayOfMonth(endDate);
                if (day1 == day2) { // 日期相同，则为前一日
                    endDate = DateUtil.offsetDay(DateUtil.offsetMonth(startDate, fee.getBillCycle()), -1);
                }
            } else if (fee.getBillStage() == BillStageEnum.BY_FIRST_MONTH.getValue()) {
                PropertyBill bill = thisService().getLastBill(cycleDto);
                if (bill == null && DateUtil.dayOfMonth(startDate) != 1) { // 账单首月，不整月划分第一账单
                    endDate = DateUtils.getMonthLastDayDate(startDate);
                } else {
                    endDate = DateUtils.getMonthLastDayDate(DateUtil.offsetMonth(startDate, fee.getBillCycle()));
                }
            } else if (fee.getBillStage() == BillStageEnum.BY_MONTH.getValue()) {
                endDate = DateUtils.getMonthLastDayDate(DateUtil.offsetMonth(startDate, fee.getBillCycle()));
            } else {
                endDate = DateUtil.offsetMonth(startDate, fee.getBillCycle());
            }
        } else {  // 账单周期按天
            endDate = DateUtil.offsetDay(startDate, fee.getBillCycle() - 1);
        }

        // 判断账单结束日期是否超出房源计费结束日期
        PropertyHouseBindFee houseBindFee = thisService().getHouseBindFee(cycleDto);
        if (houseBindFee.getEndFeeDate() != null
                && DateUtil.compare(endDate, houseBindFee.getEndFeeDate(), "yyyy-MM-dd") > 0) {
            endDate = houseBindFee.getEndFeeDate();
            String key1 = PropertyCacheConstant.BILL_CYCLE_MONTH_CACHE + ":" + cycleDto.getHouseId() + ":" + cycleDto.getFeeId();
            nestRedis.setEx(key1, 1, 10L); // 标识账单尾月不整月
        }

        return endDate;

    }


    /**
     * 根据收费标准规则计算应收金额：单位分
     *
     * @param monthDay
     * @param fee
     * @return
     */
    private long getReceivableAmt(PropertyBillMonthDay monthDay, PropertyFeeStandard fee, PropertyCycleCreateBillDto cycleDto) {
        PropertyHouses houses = propertyHousesService.getById(cycleDto.getHouseId());
        if (fee.getCycleType() == MonthDayEnum.MONTH.getValue()) { // 按月计算
            long totalPrice = fee.getPriceFen() * monthDay.getMonths() + fee.getPriceDayFen() * monthDay.getDays();
            if (fee.getCalcType() == CalcTypeEnum.QUOTA_CALCULATION.getValue()) { // 固定月租
                return totalPrice;
            } else if (fee.getCalcType() == CalcTypeEnum.CONSTRUCTION_AREA.getValue()) {
                BigDecimal amt = new BigDecimal(totalPrice)
                        .multiply(houses.getConstructionArea()).setScale(0, RoundingMode.HALF_UP);
                return amt.longValue();
            } else if (fee.getCalcType() == CalcTypeEnum.USE_AREA.getValue()) {
                BigDecimal amt = new BigDecimal(totalPrice)
                        .multiply(houses.getUseArea()).setScale(0, RoundingMode.HALF_UP);
                return amt.longValue();
            }
        } else { // 按天计算
            long countDay = monthDay.getDays();
            if (fee.getCalcType() == CalcTypeEnum.QUOTA_CALCULATION.getValue()) { // 固定月租
                return countDay * fee.getPriceFen().intValue();
            } else if (fee.getCalcType() == CalcTypeEnum.CONSTRUCTION_AREA.getValue()) {
                BigDecimal amt = new BigDecimal(countDay * fee.getPriceFen())
                        .multiply(houses.getConstructionArea()).setScale(0, RoundingMode.HALF_UP);
                return amt.longValue();
            } else if (fee.getCalcType() == CalcTypeEnum.USE_AREA.getValue()) {
                BigDecimal amt = new BigDecimal(countDay * fee.getPriceFen().intValue())
                        .multiply(houses.getUseArea()).setScale(0, RoundingMode.HALF_UP);
                return amt.longValue();
            }
        }

        throw new ServiceException("账单应收金额异常");
    }


    /**
     * 根据收费标准规则，计算账单 整月月数 和 天数
     *
     * @param startDate
     * @param endDate
     * @param fee
     * @param cycleDto
     * @return
     */
    private PropertyBillMonthDay getBillMonthsAndDays(Date startDate, Date endDate, PropertyFeeStandard fee, PropertyCycleCreateBillDto cycleDto) {
        PropertyBillMonthDay monthDay = new PropertyBillMonthDay();
        if (fee.getCycleType() == MonthDayEnum.DAY.getValue()) { // 按天计算
            monthDay.setDays(DateUtil.between(startDate, endDate, DateUnit.DAY) + 1);
            return monthDay;
        }
        String key1 = PropertyCacheConstant.BILL_CYCLE_MONTH_CACHE + ":" + cycleDto.getHouseId() + ":" + cycleDto.getFeeId();
        Integer redisFlag = nestRedis.get(key1);
        int betweenMonth = Integer.parseInt(String.valueOf(DateUtil.betweenMonth(startDate, endDate, true)));
        if (fee.getBillStage() == BillStageEnum.BY_DAY.getValue()) {
            if (redisFlag != null && redisFlag == 1) { // 截止月不整月
                if (betweenMonth > 1) { // 跨月
                    // 如：1.10 ~ 3.5 ---> 1.0 ~ 2.9 ; 2.10 ~ 3.5
                    Date prevDate = DateUtil.offsetMonth(startDate, fee.getBillCycle() - 1); // 2.10
                    monthDay.setMonths(fee.getBillCycle() - 1);
                    monthDay.setDays(DateUtil.between(prevDate, endDate, DateUnit.DAY) + 1); // 2.10 ~ 3.5
                } else { // 账单周期不足月
                    // 如：1.10 ~ 2.5
                    monthDay.setDays(DateUtil.between(startDate, endDate, DateUnit.DAY) + 1);
                }
            } else {
                monthDay.setMonths(fee.getBillCycle());
            }
        } else {
            int startDay = DateUtil.dayOfMonth(startDate);
            int endDay = DateUtil.dayOfMonth(endDate);
            int lastDay = DateUtil.dayOfMonth(DateUtil.endOfMonth(endDate));
            if (startDay == 1 && endDay == lastDay) { // 标准周期
                monthDay.setMonths(fee.getBillCycle());
            } else if (startDay != 1 && endDay != lastDay) { // 首尾不整月
                long d1 = DateUtil.between(startDate, DateUtil.endOfMonth(startDate), DateUnit.DAY) + 1;
                long d2 = DateUtil.between(DateUtil.beginOfMonth(endDate), endDate, DateUnit.DAY) + 1;

                monthDay.setMonths(this.calcMonth(startDate, endDate));
                monthDay.setDays(d1 + d2);
            } else {
                monthDay.setMonths(this.calcMonth(startDate, endDate));
                if (startDay == 1) { // 尾月不整月
                    monthDay.setDays(DateUtil.between(DateUtil.beginOfMonth(endDate), endDate, DateUnit.DAY) + 1);
                } else { // 首月不整月
                    monthDay.setDays(DateUtil.between(startDate, DateUtil.endOfMonth(startDate), DateUnit.DAY) + 1);
                }
            }

            log.info("getBillMonthsAndDays=" + monthDay);

        }

        return monthDay;
    }


    /**
     * 计算整月数
     *
     * @param startDate
     * @param endDate
     * @return
     */
    private int calcMonth(Date startDate, Date endDate) {
        int countDay = Integer.parseInt(String.valueOf(DateUtil.between(startDate, endDate, DateUnit.DAY) + 1));
        if (DateUtil.month(startDate) == DateUtil.month(endDate)) {
            int monthDay = Integer.parseInt(
                    String.valueOf(
                            DateUtil.between(DateUtil.beginOfMonth(startDate), DateUtil.endOfMonth(startDate), DateUnit.DAY) + 1
                    )
            );
            return countDay / monthDay;
        } else {
            return countDay / 30;
        }
    }


    /**
     * 查询最近一笔账单
     *
     * @param cycleDto
     * @return
     */
    @Cacheable(cacheNames = PropertyCacheConstant.BILL_LAST_CACHE + "#5", key = "'cycle:' + #cycleDto.houseId + ':' + #cycleDto.feeId", unless = "#result == null")
    public PropertyBill getLastBill(PropertyCycleCreateBillDto cycleDto) {
        LambdaQueryWrapper<PropertyBill> qryBill = new LambdaQueryWrapper<>();
        qryBill.eq(PropertyBill::getHouseId, cycleDto.getHouseId());
        qryBill.eq(PropertyBill::getFeeId, cycleDto.getFeeId());
        qryBill.in(PropertyBill::getCreateBillType, CreateBillTypeEnum.CYCLE.getValue(), CreateBillTypeEnum.BEFOREHAND.getValue());
        qryBill.orderByDesc(PropertyBill::getEndDate).last("limit 1");
        PropertyBill bill = billMapper.selectOne(qryBill);
        return bill;
    }

    /**
     * 查询房源绑定收费
     *
     * @param cycleDto
     * @return
     */
    @Cacheable(cacheNames = PropertyCacheConstant.BILL_HOUSE_BIND_CACHE + "#5", key = "#cycleDto.houseId + ':' + #cycleDto.feeId")
    public PropertyHouseBindFee getHouseBindFee(PropertyCycleCreateBillDto cycleDto) {
        LambdaQueryWrapper<PropertyHouseBindFee> qw = new LambdaQueryWrapper<>();
        qw.eq(PropertyHouseBindFee::getHouseId, cycleDto.getHouseId());
        qw.eq(PropertyHouseBindFee::getFeeStandardId, cycleDto.getFeeId());
        qw.eq(PropertyHouseBindFee::getIsDeleted, NestConstant.DB_NOT_DELETED);
        return propertyHouseBindFeeService.getOne(qw);
    }




}
