package cn.iocoder.yudao.module.system.api.bpm;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.util.corp.CorpMsgUtil;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.bpm.enums.message.BpmMessageEnum;
import cn.iocoder.yudao.module.system.api.bpm.vo.MessgeUserRespVO;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.system.api.notify.convert.message.UserMessageConvert;
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.api.wechat.dto.CorpSendMessage;
import cn.iocoder.yudao.module.system.api.wechat.dto.CorpWechatTextcard;
import cn.iocoder.yudao.module.system.api.wechat.dto.WechatMessageSaveReq;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ExpenseOrderItemVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ExpenseOrderVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.RefundSettlementOrderRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.*;
import cn.iocoder.yudao.module.system.dal.dataobject.sys.BusinessParamsConfigDO;
import cn.iocoder.yudao.module.system.dal.mysql.biz.*;
import cn.iocoder.yudao.module.system.dal.mysql.sys.BusinessParamsConfigMapper;
import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
import cn.iocoder.yudao.module.system.enums.NoticeConstants;
import cn.iocoder.yudao.module.system.enums.change.BusinessConstants;
import cn.iocoder.yudao.module.system.enums.common.BooleanEnum;
import cn.iocoder.yudao.module.system.enums.notice.NotieBusinessTypeEnum;
import cn.iocoder.yudao.module.system.enums.notice.NotifyPriorityEnum;
import cn.iocoder.yudao.module.system.enums.notice.NotifyTypeEnum;
import cn.iocoder.yudao.module.system.service.biz.ExpenseOrderService;
import cn.iocoder.yudao.module.system.service.biz.RefundSettlementOrderService;
import com.mzt.logapi.starter.annotation.LogRecord;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
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.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.APPLY_RETREAT_ASK_LEAVE_EXISTS;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.APPLY_RETREAT_CURRENT_EXPENSE_ORDER_NOT_EXISTS;
import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*;
import static cn.iocoder.yudao.module.system.enums.NoticeConstants.REFUND_SETTLEMENT_HANDLE_URL;
import static cn.iocoder.yudao.module.system.enums.change.ChangeConstants.RETREAT_DAILY_PAY_REMARK;

@Service
@Slf4j
public class BpmElderlyExpenseApiImpl implements BpmElderlyExpenseApi {

    @Resource
    private ElderlyInfoMapper elderlyInfoMapper;
    @Resource
    private ElderlyRetreatRecordMapper retreatRecordMapper;
    @Resource
    private RefundSettlementOrderMapper refundSettlementOrderMapper;

    @Resource
    private ExpenseMapper expenseMapper;

    @Resource
    private ExpenseItemMapper expenseItemMapper;

    @Resource
    private DailyExpensesMapper dailyExpensesMapper;

    @Resource
    private ExpenseOrderMapper expenseOrderMapper;


    @Resource
    private ExpenseOrderItemMapper expenseOrderItemMapper;

    @Resource
    private AdminUserMapper userMapper;
    @Resource
    private BusinessParamsConfigMapper businessParamsConfigMapper;

    @Autowired
    private NotifyMessageSendApi notifyMessageSendApi;

    @Autowired
    private ElderlyCheckInRecordMapper elderlyCheckInRecordMapper;

    @Autowired
    private ElderlyAskLeaveMapper askLeaveMapper;
    @Resource
    private RefundSettlementOrderService refundSettlementOrderService;
    @Resource
    private RefundSettlementOrderItemMapper refundSettlementOrderItemMapper;

    @Override
    @LogRecord(type = EXPENSE_SETTLEMENT_TYPE, subType = EXPENSE_SETTLEMENT_TYPE_SUB_TYPE, bizNo = "{{#elderlyInfo.id}}",
            success = EXPENSE_SETTLEMENT_INSERT_SUCCESS)
    @TenantIgnore
    @Transactional
    public void generateRefundSettlementOrder(Long businessId, Long startTenantId) {
        ElderlyRetreatRecordDO elderlyRetreatRecord = retreatRecordMapper.selectById(businessId);
        ElderlyInfoDO elderlyInfo = elderlyInfoMapper.selectById(elderlyRetreatRecord.getElderlyId());
        ElderlyCheckInRecordDO elderlyCheckInRecordDO = elderlyCheckInRecordMapper.selectOne(new LambdaQueryWrapperX<ElderlyCheckInRecordDO>()
                .eq(ElderlyCheckInRecordDO::getElderId, elderlyInfo.getId())
                .eq(ElderlyCheckInRecordDO::getCurrentFlag,0));
        // 费用截止日期
        LocalDate costDeadlineDate = elderlyRetreatRecord.getCostDeadlineDate();
        YearMonth costDeadlineYearMonth = YearMonth.from(costDeadlineDate);
        // 将费用截止日期后的账单更新为不显示
        expenseOrderMapper.update(new ExpenseOrderDO().setIsShow(Boolean.FALSE),new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eq(ExpenseOrderDO::getElderId,elderlyInfo.getId())
                .eq(ExpenseOrderDO::getPayStatus,0)
                .ge(ExpenseOrderDO::getBillingMonth,costDeadlineYearMonth));
        if (elderlyCheckInRecordDO != null){
            elderlyCheckInRecordDO.setStatus(3);
            elderlyCheckInRecordMapper.updateById(elderlyCheckInRecordDO);
        }

        //生成退款结算单
        RefundSettlementOrderDO order = new RefundSettlementOrderDO();
        order.setElderId(elderlyInfo.getId());
        order.setBusinessId(businessId);
        order.setRefundAmount(BigDecimal.ZERO);
        order.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
        order.setCreatedTime(new Date());
        order.setOrderNumber("T" + RandomUtil.randomNumbers(15));
        order.setTenantId(startTenantId);
        refundSettlementOrderMapper.insert(order);


        // 生成退款单子项,获取退住结算数据
        RefundSettlementOrderRespVO virtualRefundSettlementOrder = refundSettlementOrderService.getVirtualRefundSettlementOrder(order.getId());
        // 根据虚拟退住结算单生成实际的退住结算单项
        if (virtualRefundSettlementOrder.getOrders() != null) {
            for (ExpenseOrderVO expenseOrderVO : virtualRefundSettlementOrder.getOrders()) {
                if (expenseOrderVO.getOrderItems() != null) {
                    for (ExpenseOrderItemVO itemVO : expenseOrderVO.getOrderItems()) {
                        RefundSettlementOrderItemDO refundItem = getRefundSettlementOrderItemDO(itemVO, order);
                        // 插入数据库
                        refundSettlementOrderItemMapper.insert(refundItem);
                    }
                }
            }
        }
        assert virtualRefundSettlementOrder.getOrders() != null;
        order.setRefundableAmount(virtualRefundSettlementOrder.getOrders().get(0).getRefundableAmount());
        order.setReceivableAmount(virtualRefundSettlementOrder.getOrders().get(0).getReceivableAmount());
        order.setRefundAmount(virtualRefundSettlementOrder.getOrders().get(0).getOrderAmount());
        refundSettlementOrderMapper.updateById(order);


        //查询长者月度费项配置
        ExpenseDO expense = expenseMapper.selectList(new LambdaQueryWrapperX<ExpenseDO>()
                        .eq(ExpenseDO::getElderId, elderlyInfo.getId())
                        .eq(ExpenseDO::getTenantId, startTenantId)
                        .orderByDesc(ExpenseDO::getCreatedTime))
                .stream()
                .findFirst()
                .get();

        List<ExpenseItemDO> expenseItems = expenseItemMapper.selectList(new LambdaQueryWrapperX<ExpenseItemDO>()
                .eq(ExpenseItemDO::getExpenseId, expense.getId())
                .eq(ExpenseItemDO::getIsMonthlyExpense, BooleanEnum.TRUE.getValue())
                .eq(ExpenseItemDO::getTenantId, startTenantId));

        for (ExpenseItemDO expenseItem : expenseItems) {
            List<ExpenseItemDO> list = expenseItemMapper.selectCurrentMonthList(elderlyRetreatRecord.getRetreatDate(),
                    elderlyRetreatRecord.getElderlyId(), expenseItem.getType());
            LocalDate retreatDate = elderlyRetreatRecord.getRetreatDate();
            LocalDate firstDayOfMonth = retreatDate.withDayOfMonth(1);
            for (int i = 0; i < list.size(); i++) {
                ExpenseItemDO expenseItemDO = list.get(i);
                BigDecimal daysInCurrentMonth = BigDecimal.valueOf(elderlyRetreatRecord.getRetreatDate().lengthOfMonth());
                BigDecimal actualAmount = expenseItemDO.getActualAmount();
                LocalDate changeStartDate = expenseItemDO.getChangeStartDate();
                LocalDate changeEndDate = expenseItemDO.getChangeEndDate();

                if (null == changeEndDate && changeStartDate.isBefore(retreatDate)) {
                    changeEndDate = retreatDate;
                }

                // 变更产生的金额
                BigDecimal amount = BigDecimal.ZERO;
                // 变更整月差额
                BigDecimal changeAmount = BigDecimal.ZERO;
                if (null != changeStartDate && null != changeEndDate) {
                    long startDay = changeStartDate.toEpochDay();
                    long endDay = changeEndDate.toEpochDay();
                    BigDecimal currentDays = BigDecimal.valueOf(endDay - startDay);
                    BigDecimal currentDayAmount = actualAmount.divide(daysInCurrentMonth, 8, RoundingMode.HALF_UP);
                    amount = currentDayAmount.multiply(currentDays);
                    if (i == list.size() - 1) {
                        continue;
                    }
                    ExpenseItemDO nextItem = list.get(i + 1);
                    LocalDate lastDayOfMonth = firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth());
                    long epochDay = lastDayOfMonth.toEpochDay();
                    BigDecimal changeDay = BigDecimal.valueOf(epochDay - endDay);
                    BigDecimal changeDayAmount = nextItem.getActualAmount().divide(daysInCurrentMonth, 8, RoundingMode.HALF_UP);
                    BigDecimal currentAmount = currentDayAmount.multiply(changeDay);
                    changeAmount = changeDayAmount.multiply(changeDay);
                    BigDecimal finalAmount = currentAmount.subtract(changeAmount);

                    DailyExpensesDO dailyExpenses = new DailyExpensesDO();
                    dailyExpenses.setType(2); // 2：变更
                    dailyExpenses.setElderId(elderlyRetreatRecord.getElderlyId());
                    dailyExpenses.setAttributionBillTime(lastDayOfMonth.format(DateTimeFormatter.ofPattern("yyyy-MM")));
                    dailyExpenses.setItemId(nextItem.getId());
                    dailyExpenses.setItemCategory(nextItem.getItemCategoryName());
                    dailyExpenses.setItemName(nextItem.getItemName());
                    dailyExpenses.setPrice(finalAmount);
                    dailyExpenses.setAmount(finalAmount);
                    String currentRemark = String.format(RETREAT_DAILY_PAY_REMARK, nextItem.getItemName(), changeEndDate
                            ,changeEndDate, lastDayOfMonth);
                    dailyExpenses.setRemarks(currentRemark); //todo 补充备注
                    dailyExpenses.setCount(1);
                    dailyExpenses.setCreatedTime(new Date());
                    dailyExpenses.setAccountingTime(new Date());
                    dailyExpenses.setIsGenerateBill(BooleanEnum.FALSE.getValue());
                    dailyExpenses.setTenantId(startTenantId);
                    dailyExpenses.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                    dailyExpensesMapper.insert(dailyExpenses);
                }
            }
        }
        Map<String, Object> templateParams = new HashMap<>();
        templateParams.put("elderName", elderlyInfo.getElderName());
        List<BusinessParamsConfigDO> businessParamsConfigDOS = businessParamsConfigMapper.selectListByName(startTenantId ,NoticeConstants.REFUND_SETTLEMENT_HANDLE_NAME);

        String title = CorpMsgUtil.TITLE_TODO;
        CorpSendMessage corpSendMessage = new CorpSendMessage();
        WechatMessageSaveReq wechatMessageSaveReq = new WechatMessageSaveReq();
        CorpWechatTextcard corpTextCard = new CorpWechatTextcard();
        corpTextCard.setTitle(title);
        corpTextCard.setBtntxt(CorpMsgUtil.BTNTXT_DETAIL_NOTICE);
        corpTextCard.setUrl("");
        corpSendMessage.setTextcard(corpTextCard);
        corpSendMessage.setContentType(1);
        wechatMessageSaveReq.setTitle(title);
        wechatMessageSaveReq.setPkMessage(elderlyInfo.getId().toString());
        wechatMessageSaveReq.setPkMessage2("");
        wechatMessageSaveReq.setApplyTime(DateUtils.localDateTimeToStr(LocalDateTime.now()));

        if (null == businessParamsConfigDOS || businessParamsConfigDOS.size() == 0) return;
        businessParamsConfigDOS.forEach(businessParamsConfigDO -> {
            wechatMessageSaveReq.setTypeName(businessParamsConfigDO.getConfigName());
            corpSendMessage.setWechatMessageSaveReq(wechatMessageSaveReq);
            List<MessgeUserRespVO> userList = new ArrayList<>();
            List<MessgeUserRespVO> deptList = new ArrayList<>();
            if (cn.iocoder.yudao.framework.common.util.string.StringUtil.isNotEmptyORNull(businessParamsConfigDO.getValue())) {
                String[] split = businessParamsConfigDO.getValue().split(",");
                Set<Long> userIds = new HashSet<>();
                for (String s : split) {
                    userIds.add(Long.parseLong(s));
                }
                userList = userMapper.selectMessageUserByIds(userIds, 0);
            }
            if (cn.iocoder.yudao.framework.common.util.string.StringUtil.isNotEmptyORNull(businessParamsConfigDO.getValue2())) {
                String[] split = businessParamsConfigDO.getValue2().split(",");
                Set<Long> deptIds = new HashSet<>();
                for (String s : split) {
                    deptIds.add(Long.parseLong(s));
                }
                deptList = userMapper.selectMessageUserByIds(deptIds, 1);
            }
            List<MessgeUserRespVO> mergeList = new ArrayList<>();
            Map<Long, Long> userIdsMap = new HashMap<>();
            for (int i = 0; i < userList.size(); i++) {
                MessgeUserRespVO messgeUserRespVO = userList.get(i);
                userIdsMap.put(messgeUserRespVO.getId(), messgeUserRespVO.getId());
                mergeList.add(messgeUserRespVO);
            }
            for (int i = 0; i < deptList.size(); i++) {
                if (!userIdsMap.containsKey(deptList.get(i).getId())) {
                    mergeList.add(deptList.get(i));
                }
            }
            String templateCode = "";
            String url = "";
            if (NoticeConstants.REFUND_SETTLEMENT_HANDLE_NAME.equals(businessParamsConfigDO.getConfigName())) {
                // TODO 参数
                url = String.format(REFUND_SETTLEMENT_HANDLE_URL, order.getId(), elderlyInfo.getId(), startTenantId);
                templateCode = BpmMessageEnum.REFUND_SETTLEMENT_HANDLE.getTemplateCode();
            }
            for (int i = 0; i < mergeList.size(); i++) {
                MessgeUserRespVO messgeUserRespVO = mergeList.get(i);
                NotifySendSingleToUserReqDTO checkInHandleReqdto = UserMessageConvert.INSTANCE.messageConvert(messgeUserRespVO.getId(),
                        elderlyInfo.getId().toString(), null, url, templateCode, templateParams,
                        NotieBusinessTypeEnum.ORDINARY.getValue(), NotifyTypeEnum.NOTIFY_TODO.getValue(),
                        NotifyPriorityEnum.HIGH.getValue(), 0, messgeUserRespVO.getTenantId());
                corpSendMessage.setTouser(messgeUserRespVO.getCorpUserId());
                wechatMessageSaveReq.setCorpUserId(messgeUserRespVO.getCorpUserId());
                wechatMessageSaveReq.setStartTenantId(messgeUserRespVO.getTenantId());
                wechatMessageSaveReq.setTenantName(messgeUserRespVO.getAbbreviation());
                notifyMessageSendApi.sendMessageAndCorpMessage(checkInHandleReqdto, corpSendMessage);
            }
        });

    }

    @NotNull
    private static RefundSettlementOrderItemDO getRefundSettlementOrderItemDO(ExpenseOrderItemVO itemVO, RefundSettlementOrderDO order) {
        RefundSettlementOrderItemDO refundItem = new RefundSettlementOrderItemDO();
        refundItem.setRefundSettlementOrderId(order.getId());
        refundItem.setItemName(itemVO.getItemName());
        refundItem.setPrice(itemVO.getPrice() != null ? itemVO.getPrice().abs() : BigDecimal.ZERO);
        refundItem.setCount(itemVO.getCount() != null ? itemVO.getCount() : 1);
        refundItem.setRemarks(itemVO.getRemarks());
        refundItem.setRefundAmount(itemVO.getAmount());
        refundItem.setActualRefundAmount(itemVO.getAmount());
        refundItem.setSourceItemId(itemVO.getItemId());
        refundItem.setTenantId(TenantContextHolder.getTenantId());
        // 根据费用类型设置type字段
        if (itemVO.getIsDeposit() != null && itemVO.getIsDeposit() == 1) {
            // 押金费用
            refundItem.setType(4);
        } else if (itemVO.getIsMonthlyExpense() != null && itemVO.getIsMonthlyExpense() == 1) {
            // 月度费用
            refundItem.setType(1);
        } else if (itemVO.getIsOneTimeFee() != null && itemVO.getIsOneTimeFee() == 1) {
            // 一次性费用
            refundItem.setType(3);
        } else if (itemVO.getDailyExpenseType() != null && itemVO.getDailyExpenseType() > 0) {
            // 日常杂费（包括日常、变更、外出,长护险等）
            refundItem.setType(2);
        }else {
            refundItem.setType(5);
        }

        // 设置状态为未结算
        refundItem.setStatus(false);
        return refundItem;
    }

    @Override
    public void applyRetreatCheck(Long elderId,LocalDate costDeadlineDate){
        ElderlyInfoDO elderlyInfoDO = elderlyInfoMapper.selectById(elderId);
        if(elderlyInfoDO.getInStatusType() == 1){
            YearMonth deadLineYearMonth = YearMonth.from(costDeadlineDate);
            List<ExpenseOrderDO> expenseOrderDOS = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                    .eq(ExpenseOrderDO::getElderId, elderId)
                    .eq(ExpenseOrderDO::getBillingMonth, deadLineYearMonth)
            );
            if(CollectionUtil.isEmpty(expenseOrderDOS)){
                throw exception(APPLY_RETREAT_CURRENT_EXPENSE_ORDER_NOT_EXISTS);
            }
        }
        List<ElderlyAskLeaveDO> elderlyAskLeaveDOS = askLeaveMapper.selectList(new LambdaQueryWrapperX<ElderlyAskLeaveDO>()
                .eq(ElderlyAskLeaveDO::getElderId, elderId)
                .isNull(ElderlyAskLeaveDO::getUpdateDate));
        if(CollectionUtil.isNotEmpty(elderlyAskLeaveDOS)){
            throw exception(APPLY_RETREAT_ASK_LEAVE_EXISTS);
        }
    }
}
