package cn.iocoder.yudao.module.system.service.biz;

import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ElderlyTempOutCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ElderlyTempOutPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ElderlyTempOutRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ElderlyTempOutUpdateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ExpenseItemRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ExpenseOrderSaveVO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.DailyExpensesDO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.ElderlyTempOutDO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.ElderlyInfoDO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.ExpenseItemDO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.ExpenseOrderDO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.ExpenseOrderItemDO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.ElderlyContractDO;
import cn.iocoder.yudao.module.system.dal.mysql.biz.*;
import cn.iocoder.yudao.module.system.enums.change.BusinessConstants;
import cn.iocoder.yudao.module.system.enums.common.BooleanEnum;
import cn.iocoder.yudao.module.system.service.biz.ExpenseOrderService;
import org.springframework.beans.factory.annotation.Autowired;
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.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exceptionCustomMsg;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.COMMON_ERROR;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.COMMON_NOT_FOUND;


@Service
public class ElderlyTempOutServiceImpl implements ElderlyTempOutService {

    @Resource
    private ElderlyTempOutMapper elderlyTempOutMapper;

    @Resource
    private BuildService buildService;

    @Resource
    private ElderlyInfoMapper elderlyInfoMapper;

    @Autowired
    private ExpenseOrderMapper expenseOrderMapper;

    @Autowired
    private ExpenseOrderItemMapper expenseOrderItemMapper;

    @Autowired
    private DailyExpensesMapper dailyExpensesMapper;

    @Autowired
    private ExpenseMapper expenseMapper;

    @Autowired
    private ExpenseItemMapper expenseItemMapper;

    @Autowired
    private ExpenseOrderService expenseOrderService;

    @Autowired
    private ElderlyContractMapper elderlyContractMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(ElderlyTempOutCreateReqVO createReq) {
        ElderlyTempOutDO elderlyTempOutDO = elderlyTempOutMapper.selectOne(new LambdaQueryWrapperX<ElderlyTempOutDO>()
                .eq(ElderlyTempOutDO::getOutStatus, 1)
                .eq(ElderlyTempOutDO::getElderId, createReq.getElderId()));
        if(elderlyTempOutDO != null){
            throw exceptionCustomMsg(COMMON_ERROR,"存在离住记录不得重复离住");
        }
        ElderlyTempOutDO dataObject = BeanUtils.toBean(createReq, ElderlyTempOutDO.class);
        if (createReq.getOutTime() != null) {
            dataObject.setOutTime(LocalDateTime.ofInstant(createReq.getOutTime().toInstant(), ZoneId.systemDefault()));
        }
        // create 接口仅允许设置 outTime
        dataObject.setComeTime(null);
        dataObject.setOutStatus(1);
        dataObject.setOutMonth(YearMonth.from(dataObject.getOutTime()).toString());
        elderlyTempOutMapper.insert(dataObject);

        // 离住：出院当月若月账单已生成，则生成退费冲减；未生成则不处理
        if (dataObject.getOutTime() != null && dataObject.getElderId() != null) {
            handleOutTimeChanged(dataObject.getElderId(), dataObject.getTenantId(), dataObject.getOutTime());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(ElderlyTempOutUpdateReqVO updateReq) {
        ElderlyTempOutDO dbTempOut = elderlyTempOutMapper.selectById(updateReq.getId());
        if (dbTempOut == null) {
            throw exception(COMMON_NOT_FOUND);
        }
        // 仅离住状态允许更新一次（返院后只能查看）
        if (!Objects.equals(dbTempOut.getOutStatus(), 1)) {
            return;
        }

        // update 接口仅允许设置 comeTime
        if (updateReq.getComeTime() == null) {
            return;
        }
        LocalDateTime comeTime = LocalDateTime.ofInstant(updateReq.getComeTime().toInstant(), ZoneId.systemDefault());

        ElderlyTempOutDO updateDO = new ElderlyTempOutDO();
        updateDO.setId(updateReq.getId());
        updateDO.setComeTime(comeTime);
        updateDO.setOutStatus(2);
        updateDO.setOutMonth(YearMonth.from(updateDO.getComeTime()).toString());
        elderlyTempOutMapper.updateById(updateDO);

        Long elderId = dbTempOut.getElderId();
        Long tenantId = dbTempOut.getTenantId();
        if (elderId == null) {
            return;
        }

        // 返院：返院当月，未生成则生成月账单并按天计费；已生成则补固定费
        handleComeTimeChanged(elderId, tenantId, comeTime);
    }

    private void handleOutTimeChanged(Long elderId, Long tenantId, LocalDateTime outTime) {
        YearMonth billYm = YearMonth.from(outTime);
        String billingMonth = billYm.format(DateTimeFormatter.ofPattern("yyyy-MM"));

        ExpenseOrderDO monthOrder = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eq(ExpenseOrderDO::getElderId, elderId)
                .eq(ExpenseOrderDO::getBillingMonth, billingMonth)
                .in(ExpenseOrderDO::getType, 1,2));
        if (monthOrder == null) {
            return;
        }
        if (Boolean.TRUE.equals(monthOrder.getConfirmStatus()) || Boolean.TRUE.equals(monthOrder.getIsLock())) {
            return;
        }

        List<ExpenseOrderItemDO> orderItems = expenseOrderItemMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderItemDO>()
                .eq(ExpenseOrderItemDO::getExpenseOrderId, monthOrder.getId())
                .eq(ExpenseOrderItemDO::getExpenseSource, BusinessConstants.EXPENSE_ITEM));
        if (CollectionUtil.isEmpty(orderItems)) {
            return;
        }

        LocalDate outDate = outTime.toLocalDate();
        LocalDate monthEnd = billYm.atEndOfMonth();
        if (outDate.isAfter(monthEnd)) {
            return;
        }

        int refundDays = monthEnd.getDayOfMonth() - outDate.getDayOfMonth() + 1;
        int daysInMonth = billYm.lengthOfMonth();

        for (ExpenseOrderItemDO item : orderItems) {
            if (item.getType() == null) {
                continue;
            }
            // 只处理“月固定费用”(床位费/护理费/膳食费/服务费...)，排除押金/一次性等
            if (!item.getExpenseSource().equals(BusinessConstants.EXPENSE_ITEM)) {
                continue;
            }

            BigDecimal monthAmount = item.getTotalAmount() == null ? BigDecimal.ZERO : item.getTotalAmount();
            if (monthAmount.compareTo(BigDecimal.ZERO) == 0) {
                continue;
            }
            BigDecimal dayPrice = monthAmount.divide(BigDecimal.valueOf(daysInMonth), 8, RoundingMode.DOWN);
            BigDecimal refundAmount = dayPrice.multiply(BigDecimal.valueOf(refundDays)).setScale(2, RoundingMode.HALF_UP).negate();
            if (refundAmount.compareTo(BigDecimal.ZERO) == 0) {
                continue;
            }

            // 离住冲减不生成日常费用记录：直接写入账单明细（expenseSource=expense_item，金额为负数）
            ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
            orderItem.setExpenseOrderId(monthOrder.getId());
            orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
            orderItem.setExpenseSource(BusinessConstants.EXPENSE_ITEM);
            orderItem.setCount(refundDays);
            orderItem.setItemName(item.getItemName());
            orderItem.setItemCategoryName(item.getItemCategoryName());
            orderItem.setPrice(dayPrice);
            orderItem.setActualPrice(dayPrice);
            orderItem.setTotalAmount(refundAmount);
            orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
            orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
            orderItem.setCreatedTime(new Date());
            orderItem.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
            orderItem.setTenantId(tenantId);
            orderItem.setStartDate(outDate);
            orderItem.setEndDate(monthEnd);
            orderItem.setDescription("离住退费冲减:" + outDate + "至" + monthEnd + "共" + refundDays + "天," + item.getItemName());
            orderItem.setType(item.getType());
            orderItem.setItemId(item.getItemId());
            orderItem.setIsMonthlyExpense(BooleanEnum.TRUE.getValue());
            expenseOrderItemMapper.insert(orderItem);
            BigDecimal newAmount = monthOrder.getActualAmount() == null ? BigDecimal.ZERO : monthOrder.getActualAmount();
            newAmount = newAmount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
            monthOrder.setActualAmount(newAmount);
            expenseOrderMapper.updateById(monthOrder);
        }
    }

    /**
     * 返院：返院当月，未生成则生成月账单并按天计费；已生成则补固定费
     * @param elderId
     * @param tenantId
     * @param comeTime
     */
    private void handleComeTimeChanged(Long elderId, Long tenantId, LocalDateTime comeTime) {
        YearMonth billYm = YearMonth.from(comeTime);
        String billingMonth = billYm.format(DateTimeFormatter.ofPattern("yyyy-MM"));

        ExpenseOrderDO monthOrder = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eq(ExpenseOrderDO::getElderId, elderId)
                .eq(ExpenseOrderDO::getBillingMonth, billingMonth)
                .eq(ExpenseOrderDO::getType, 2));

        // 固定费用来源：不从账单明细里找（因为返院月的账单可能只有日常费用），统一从费用项生成逻辑取
        List<ExpenseItemDO> fixedItems = expenseItemMapper.generateMonthlyBill(elderId, billingMonth, BooleanEnum.FALSE.getValue());
        if (CollectionUtil.isEmpty(fixedItems)) {
            fixedItems = expenseItemMapper.generateMonthlyBill(elderId, billingMonth, BooleanEnum.TRUE.getValue());
        }

        LocalDate comeDate = comeTime.toLocalDate();
        LocalDate monthStart = billYm.atDay(1);
        LocalDate monthEnd = billYm.atEndOfMonth();
        if (comeDate.isBefore(monthStart) || comeDate.isAfter(monthEnd)) {
            return;
        }

        // 合同到期月：返院补费区间终点取“月末 与 合同到期日”较早者；到期后月份不再补固定费
        LocalDate actualEndDate = monthEnd;
        ElderlyContractDO latestContract = elderlyContractMapper.selectOne(new LambdaQueryWrapperX<ElderlyContractDO>()
                .eq(ElderlyContractDO::getElderId, elderId)
                .orderByDesc(ElderlyContractDO::getCreatedTime)
                .last("LIMIT 1"));
        if (latestContract != null && latestContract.getExpireTime() != null) {
            LocalDate contractExpireDate = latestContract.getExpireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            if (contractExpireDate.isBefore(monthStart)) {
                return;
            }
            if (!contractExpireDate.isAfter(monthEnd)) {
                actualEndDate = contractExpireDate;
            }
        }

        if (actualEndDate.isBefore(comeDate)) {
            return;
        }

        // 账单不存在：新建月度账单，并将固定费用（按返院日至实际截止日折算）加入账单
        if (monthOrder == null) {
            int addDays = (int) (actualEndDate.toEpochDay() - comeDate.toEpochDay() + 1);
            int daysInMonth = billYm.lengthOfMonth();
            final LocalDate finalActualEndDate = actualEndDate;
            List<ExpenseItemRespVO> items = fixedItems.stream().map(item -> {
                ExpenseItemRespVO respVO = new ExpenseItemRespVO();
                respVO.setExpenseType(0);
                respVO.setExpenseSource(BusinessConstants.EXPENSE_ITEM);
                respVO.setSourceExpenseItemId(item.getId());

                BigDecimal monthAmount = item.getActualAmount() == null ? BigDecimal.ZERO : item.getActualAmount();
                BigDecimal dayPrice = BigDecimal.ZERO;
                BigDecimal addAmount = BigDecimal.ZERO;
                if (monthAmount.compareTo(BigDecimal.ZERO) != 0 && daysInMonth > 0 && addDays > 0) {
                    dayPrice = item.getIsDayCalculate() == 1 ? item.getActualAmount() : monthAmount.divide(BigDecimal.valueOf(daysInMonth), 8, RoundingMode.DOWN);
                    addAmount = dayPrice.multiply(BigDecimal.valueOf(addDays)).setScale(2, RoundingMode.HALF_UP);
                }

                // 返院当月：按“返院日~实际截止日(含当天)”折算总金额；price 按日单价回传，便于与账单已存在分支一致
                respVO.setPrice(dayPrice.setScale(2, RoundingMode.HALF_UP));
                respVO.setTotalAmount(addAmount);

                respVO.setItemName(item.getItemName());
                respVO.setItemCategory(item.getItemCategoryName());
                respVO.setCount(addDays);
                respVO.setStartTime(comeDate.toString());
                respVO.setEndTime(finalActualEndDate.toString());
                respVO.setType(item.getType());
                return respVO;
            }).collect(Collectors.toList());

            ExpenseOrderSaveVO saveVO = new ExpenseOrderSaveVO();
            saveVO.setElderId(elderId);
            saveVO.setBillingMonth(billingMonth);
            saveVO.setItems(items);
            saveVO.setTenantId(tenantId);
            expenseOrderService.generateExpenseBill(saveVO);
            return;
        }

        // 账单已存在：仅把固定费用追加到该账单中（即使当前账单只有日常费用）
        if (Boolean.TRUE.equals(monthOrder.getConfirmStatus()) || Boolean.TRUE.equals(monthOrder.getIsLock())) {
            return;
        }

        int addDays = (int) (actualEndDate.toEpochDay() - comeDate.toEpochDay() + 1);
        int daysInMonth = billYm.lengthOfMonth();

        for (ExpenseItemDO item : fixedItems) {
            if (item.getType() == null) {
                continue;
            }
            if (Objects.equals(item.getType(), 8)) {
                continue;
            }

            BigDecimal monthAmount = item.getActualAmount() == null ? BigDecimal.ZERO : item.getActualAmount();
            if (monthAmount.compareTo(BigDecimal.ZERO) == 0) {
                continue;
            }

            BigDecimal dayPrice = item.getIsDayCalculate() == 1 ? item.getActualAmount() : monthAmount.divide(BigDecimal.valueOf(daysInMonth), 8, RoundingMode.DOWN);
            BigDecimal addAmount = dayPrice.multiply(BigDecimal.valueOf(addDays)).setScale(2, RoundingMode.HALF_UP);
            if (addAmount.compareTo(BigDecimal.ZERO) == 0) {
                continue;
            }

            ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
            orderItem.setExpenseOrderId(monthOrder.getId());
            orderItem.setSourceExpenseItemId(item.getId());
            orderItem.setExpenseSource(BusinessConstants.EXPENSE_ITEM);
            orderItem.setCount(addDays);
            orderItem.setItemName(item.getItemName());
            orderItem.setItemCategoryName(item.getItemCategoryName());
            orderItem.setPrice(dayPrice);
            orderItem.setActualPrice(dayPrice);
            orderItem.setTotalAmount(addAmount);
            orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
            orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
            orderItem.setCreatedTime(new Date());
            orderItem.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
            orderItem.setTenantId(tenantId);
            orderItem.setStartDate(comeDate);
            orderItem.setEndDate(actualEndDate);
            orderItem.setDescription("返院恢复固定费:" + comeDate + "至" + actualEndDate + "共" + addDays + "天," + item.getItemName());
            orderItem.setType(item.getType());
            orderItem.setItemId(item.getItemId());
            // 标注月度费用（该字段为非持久化扩展字段，用于返回前端展示）
            orderItem.setIsMonthlyExpense(BooleanEnum.TRUE.getValue());
            expenseOrderItemMapper.insert(orderItem);

            BigDecimal newAmount = monthOrder.getActualAmount() == null ? BigDecimal.ZERO : monthOrder.getActualAmount();
            newAmount = newAmount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
            monthOrder.setActualAmount(newAmount);
            expenseOrderMapper.updateById(monthOrder);
        }
    }

    @Override
    public void delete(Long id) {
        elderlyTempOutMapper.deleteById(id);
    }

    @Override
    public PageResult<ElderlyTempOutRespVO> page(ElderlyTempOutPageReqVO reqVO) {
        PageResult<ElderlyTempOutDO> pageResult = elderlyTempOutMapper.selectPage(reqVO);
        PageResult<ElderlyTempOutRespVO> respPage = BeanUtils.toBean(pageResult, ElderlyTempOutRespVO.class);
        for (int i = 0; i < pageResult.getList().size(); i++) {
            ElderlyTempOutDO dataObject = pageResult.getList().get(i);
            ElderlyTempOutRespVO record = respPage.getList().get(i);
            if (dataObject.getOutTime() != null) {
                record.setOutTime(java.util.Date.from(dataObject.getOutTime().atZone(ZoneId.systemDefault()).toInstant()));
            }
            if (dataObject.getComeTime() != null) {
                record.setComeTime(java.util.Date.from(dataObject.getComeTime().atZone(ZoneId.systemDefault()).toInstant()));
            }
            if(record.getElderId() != null){
                record.setElderName(elderlyInfoMapper.selectById(record.getElderId()).getElderName());
            }
            if (record.getBedId() != null) {
                record.setBedName(buildService.getFullBedNameByBedId(record.getBedId()));
            }
        }
        return respPage;
    }

    @Override
    public ElderlyTempOutRespVO get(Long id) {
        ElderlyTempOutDO dataObject = elderlyTempOutMapper.selectById(id);
        if (dataObject == null) {
            return null;
        }
        ElderlyTempOutRespVO respVO = BeanUtils.toBean(dataObject, ElderlyTempOutRespVO.class);
        if (dataObject.getOutTime() != null) {
            respVO.setOutTime(java.util.Date.from(dataObject.getOutTime().atZone(ZoneId.systemDefault()).toInstant()));
        }
        if (dataObject.getComeTime() != null) {
            respVO.setComeTime(java.util.Date.from(dataObject.getComeTime().atZone(ZoneId.systemDefault()).toInstant()));
        }
        if (respVO.getElderId() != null) {
            ElderlyInfoDO elderlyInfo = elderlyInfoMapper.selectById(respVO.getElderId());
            if (elderlyInfo != null) {
                respVO.setElderName(elderlyInfo.getElderName());
            }
        }
        if (respVO.getBedId() != null) {
            respVO.setBedName(buildService.getFullBedNameByBedId(respVO.getBedId()));
        }
        return respVO;
    }
}

