package cn.iocoder.yudao.module.bpm.service.oa;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.enums.ShopCommonEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.api.oa.vo.contract.BpmOAContractVO;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.contract.*;
import cn.iocoder.yudao.module.bpm.convert.oa.BpmOAContractConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAContractDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAContractMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmHistoryProcessInstanceService;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.hrm.api.crmbusiness.BusinessApi;
import cn.iocoder.yudao.module.hrm.api.crmcontract.ContractApi;
import cn.iocoder.yudao.module.hrm.api.crmcontract.dto.CrmContractProductDTO;
import cn.iocoder.yudao.module.hrm.api.crmcustomer.CrmCustomerApi;
import cn.iocoder.yudao.module.hrm.api.crmcustomer.dto.CrmCustomerDTO;
import cn.iocoder.yudao.module.hrm.api.userlivetree.UserLiveTreeApi;
import cn.iocoder.yudao.module.product.api.storeproduct.StoreProductApi;
import cn.iocoder.yudao.module.product.api.storeproductattrvalue.StoreProductAttrValueApi;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.SECOND_MILLIS;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.OA_CONTRACT_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.PRODUCT_STOCK_LESS;

/**
 * OA 合同审批 Service 实现类
 *
 * @author 符溶馨
 */
@Service
@Validated
@Slf4j
public class BpmOAContractServiceImpl extends BpmOABaseService implements BpmOAContractService {

    /**
     * OA 合同审批对应的流程定义 KEY
     */
    public static final String PROCESS_KEY = "oa_contract_2";

    @Resource
    private BpmOAContractMapper contractMapper;

    @Resource
    private BpmProcessInstanceService processInstanceService;

    @Resource
    private BpmHistoryProcessInstanceService historyProcessInstanceService;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private AdminUserApi userApi;

    @Resource
    private DeptApi deptApi;

    @Resource
    private StoreProductAttrValueApi storeProductAttrValueApi;

    @Resource
    private StoreProductApi storeProductApi;

    @Resource
    private ContractApi contractApi;

    @Resource
    private CrmCustomerApi customerApi;
    @Resource
    private BusinessApi businessApi;
    @Resource
    private UserLiveTreeApi userLiveTreeApi;

    @Override
    public Long createContract(Long userId, BpmOAContractCreateReqVO createReqVO) {
        // 新增合同DO
        BpmOAContractDO contract = BpmOAContractConvert.INSTANCE.convert(createReqVO)
                .setUserId(userId)
                .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult())
                .setStatus(1);

        // 自动生成合同编号
        // 获取当前日期
        String now = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String key = "bpm_contract_no_" + now;
        // 获取分布式锁
        String LOCK_KEY = "lock:rental:order:create";
        RLock lock = redissonClient.getLock(LOCK_KEY);
        try {
            lock.lock();
            // redis 获取当天订单号
            String no = stringRedisTemplate.opsForValue().get(key);
            if (no != null) {
                no = "HT" + now + String.format("%03d", Integer.parseInt(no) + 1);
                // redis 缓存订单号
                stringRedisTemplate.opsForValue().increment(key, 1);
            } else {
                no = "HT" + now + String.format("%03d", 1);
                // redis 缓存订单号
                stringRedisTemplate.opsForValue().set(key, "1", 1, TimeUnit.DAYS);
            }
            // 设置合同编号
            contract.setContractNo(no);
        } catch (Exception ex) {
            log.error("[messageResend][执行异常]", ex);
        } finally {
            lock.unlock();
        }

        //插入OA 合同审批
        contractMapper.insert(contract);

        // 发起 BPM 流程
        Map<String, Object> processInstanceVariables = new HashMap<>();
        String processInstanceId = processInstanceService.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
                        .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(contract.getId())));

        // 将工作流的编号，更新到 OA 请假单中
        contractMapper.updateById(new BpmOAContractDO().setId(contract.getId()).setProcessInstanceId(processInstanceId));

        // 判断是否为重新发起的流程
        if (createReqVO.getProcessInstanceId() != null && createReqVO.getResult() == 3) {

            historyProcessInstanceService.createHistoryProcessInstance(processInstanceId, createReqVO.getProcessInstanceId());
        }

        List<UploadUserFile> fileItems = createReqVO.getFileItems();
        //这里的逻辑，如果fileItems不为空，且有数据，那么说明是上传了附件的，则需要更工作流文件表对应的实例Id
        if (fileItems != null && !fileItems.isEmpty()) {
            uploadBpmFileProcessInstanceId(processInstanceId, fileItems);
        }

        // 同步插入关联产品记录
        createContractProductList(contract.getId(), createReqVO.getContractProducts());

        return contract.getId();
    }

    @Override
    public void updateContract(BpmOAContractDO updateReqVO) {
        validateLeaveExists(updateReqVO.getId());

        //更新合同
        contractMapper.updateById(updateReqVO);
    }

    @Override
    public void updateInvoiceMoney(Long contractId, BigDecimal amount) {

        contractMapper.update(null, new LambdaUpdateWrapper<BpmOAContractDO>()
                .setSql("invoice_money = invoice_money - " + amount)
                .eq(BpmOAContractDO::getId, contractId));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateContractResult(String processInstanceId, Long id, Integer result) {

        BpmOAContractDO contract = validateLeaveExists(id);
        contractMapper.updateById(new BpmOAContractDO().setId(id).setResult(result));

        //审核通过 （最后节点）
        if (BpmProcessInstanceResultEnum.APPROVE.getResult().equals(result)) {

            ProcessInstance instance = processInstanceService.getProcessInstance(processInstanceId);

            if (instance.isEnded()) {
                // 审批通过后，更新客户成交信息
                customerApi.updateCustomerPurchaseTotal(new CrmCustomerDTO()
                        .setId(contract.getCustomerId())
                        .setDealStatus(ShopCommonEnum.IS_STATUS_1.getValue())
                        .setDealTime(LocalDateTime.now())
                        .setPurchaseTotal(contract.getContractMoney()));

                // 审批通过后，更新商机状态
                businessApi.updateIsEnd(contract.getBusinessId(), ShopCommonEnum.IS_STATUS_1.getValue());
            }
        }

        // -- 自己取消
        // -- 审核拒绝
        if (BpmProcessInstanceResultEnum.REJECT.getResult().equals(result)
                || BpmProcessInstanceResultEnum.CANCEL.getResult().equals(result)
                || BpmProcessInstanceResultEnum.BACK.getResult().equals(result)) {

            // 获取合同商品详情
            List<CrmContractProductDTO> productDTOS = contractApi.getProduct(id).getCheckedData();

            for (CrmContractProductDTO product : productDTOS) {
                // 回复商品库存
                this.inProductStock(product.getNums(), product.getProductId(), product.getProductAttrUnique());
            }
        }
    }

    private BpmOAContractDO validateLeaveExists(Long id) {
        BpmOAContractDO contractDO = contractMapper.selectById(id);
        if (contractDO == null) {
            throw exception(OA_CONTRACT_NOT_EXISTS);
        }
        return contractDO;
    }

    @Override
    public BpmOAContractDO getContract(Long id) {

        return contractMapper.selectById(id);
    }

    @Override
    public BpmOAContractDO getByProcessInstanceId(String processInstanceId) {

        List<BpmOAContractDO> bpmOAContractDOS = contractMapper.selectList(BpmOAContractDO::getProcessInstanceId, processInstanceId);
        if (CollectionUtil.isEmpty(bpmOAContractDOS)) {
            return null;
        }
        return bpmOAContractDOS.get(0);
    }

    @Override
    public List<BpmOAContractDO> getListByDeptId(List<Long> userIds) {

        return contractMapper.selectList(new LambdaQueryWrapperX<BpmOAContractDO>()
                .eq(BpmOAContractDO::getResult, BpmProcessInstanceResultEnum.APPROVE.getResult())
                .inIfPresent(BpmOAContractDO::getUserId, userIds)
                .eq(BpmOAContractDO::getContractType, 1)
                .apply("return_money > invoice_money"));
    }

    @Override
    public PageResult<BpmOAContractRespVO> getContractPage(BpmOAContractPageReqVO pageReqVO) {

        List<Long> userIds = new ArrayList<>();
        if ("my".equals(pageReqVO.getRelation())) {
            userIds.add(getLoginUserId());
        } else if ("sub".equals(pageReqVO.getRelation())) {
            // 查询当前用户 所有下级用户编号
            userIds.addAll(userLiveTreeApi.getItemIdsByUserId(getLoginUserId()).getCheckedData());
        }
        if (CollUtil.isEmpty(userIds)) {
            return PageResult.empty();
        }
        return contractMapper.selectPage(pageReqVO, userIds);
    }

    @Override
    public List<BpmOAContractRespVO> getListByProcessInstanceIds(List<String> processInstanceIds) {

        List<BpmOAContractDO> bpmOAContractDOS = contractMapper.selectList(new LambdaQueryWrapperX<BpmOAContractDO>()
                .inIfPresent(BpmOAContractDO::getProcessInstanceId, processInstanceIds));

        List<BpmOAContractRespVO> respVOS = BeanUtils.toBean(bpmOAContractDOS, BpmOAContractRespVO.class);
        // 获取用户和签约人信息
        List<Long> userIds = bpmOAContractDOS.stream()
                .flatMap(item -> Stream.of(item.getUserId(), item.getSignatoryId()))
                .collect(Collectors.toList());
        Map<Long, AdminUserRespDTO> userMap = userApi.getUserMap(userIds);

        // 获取公司信息
        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(bpmOAContractDOS, BpmOAContractDO::getCompanyId));

        respVOS.forEach(respVO -> {
            // 设置创建人、签约人名称
            respVO.setSignatoryName(userMap.get(respVO.getSignatoryId()) != null ? userMap.get(respVO.getSignatoryId()).getNickname() : null);
            respVO.setCreateName(userMap.get(respVO.getUserId()) != null ? userMap.get(respVO.getUserId()).getNickname() : null);

            // 设置公司名称
            respVO.setCompanyName(deptMap.get(respVO.getCompanyId()) != null ? deptMap.get(respVO.getCompanyId()).getName() : null);
        });

        return respVOS;
    }

    @Override
    public ContractStatisticsRespVO getContractStatistics(Long userId, String relation) {

        ContractStatisticsRespVO respVO = contractMapper.selectContractStatistics(userId, relation);

        // 设置数量、金额百分比
        respVO.setCountPercentage(calculatePercentageChange(respVO.getTodayAdditions(), respVO.getYesterdayAdditions()));
        respVO.setMoneyPercentage(calculatePercentageChange(respVO.getTodayAddMoney(), respVO.getYesterdayAddMoney()));
        return respVO;
    }

    @Override
    public List<ContractStatisticsRespVO> getContractStatisticsListByUserIds(List<Long> userIds, LocalDateTime[] createTime) {

        return contractMapper.selectContractStatisticsListByUserIds(userIds, createTime);
    }

    @Override
    public List<BpmOAContractDO> getContractList(BpmOAContractVO respVO) {
        Date[] dateArray = new Date[]{respVO.getStarTime(), respVO.getEndTime()};
        if (CollUtil.isEmpty(respVO.getUserId())) {
            return Collections.emptyList();
        }
        return contractMapper.selectList(new LambdaQueryWrapperX<BpmOAContractDO>()
                .inIfPresent(BpmOAContractDO::getUserId, respVO.getUserId())
                .eqIfPresent(BpmOAContractDO::getContractType, respVO.getContractType())
                .betweenIfPresent(BpmOAContractDO::getSigningDate, dateArray)
                .eq(BpmOAContractDO::getResult, BpmProcessInstanceResultEnum.APPROVE.getResult()));
    }

    @Override
    public List<BpmOAContractDO> getListByCustomerId(Long customerId) {

        return contractMapper.selectList(new LambdaQueryWrapperX<BpmOAContractDO>()
                .eq(BpmOAContractDO::getCustomerId, customerId)
                .eq(BpmOAContractDO::getResult, BpmProcessInstanceResultEnum.APPROVE.getResult()));
    }

    @Override
    public void updateSettlementFlagByIds(List<Long> contractIds) {
        contractMapper.update(new BpmOAContractDO().setSettlementFlag(1), new LambdaQueryWrapper<BpmOAContractDO>()
                .in(BpmOAContractDO::getId, contractIds));
    }

    @Override
    public List<BpmOAContractDO> getList(String relation, Integer contractType, String contractName) {
        List<Long> userIds = new ArrayList<>();
        Long id = getLoginUserId();
        if ("my".equals(relation)) {
            userIds.add(id);
        } else if ("sub".equals(relation)) {
            // 查询当前用户 所有下级用户编号
            userIds.addAll(userLiveTreeApi.getItemIdsByUserId(id).getCheckedData());
        }
        userIds.add(id);
        if (CollUtil.isNotEmpty(userIds)) {
            return contractMapper.selectList(new LambdaQueryWrapperX<BpmOAContractDO>()
                    .in(BpmOAContractDO::getUserId, userIds)
                    .eqIfPresent(BpmOAContractDO::getContractType, contractType)
                    .likeIfPresent(BpmOAContractDO::getContractName, contractName)
                    .eq(BpmOAContractDO::getResult, BpmProcessInstanceResultEnum.APPROVE.getResult()));
        }
        return Collections.emptyList();
    }

    public static BigDecimal calculatePercentageChange(Object today, Object yesterday) {

        // 转换为 BigDecimal 进行高精度计算
        BigDecimal bigToday = new BigDecimal(today.toString());
        BigDecimal bigYesterday = new BigDecimal(yesterday.toString());

        if (bigYesterday.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        }

        // 计算百分比变化
        BigDecimal difference = bigToday.subtract(bigYesterday);  // a - b
        BigDecimal percentageChange = difference.divide(bigYesterday, 2, RoundingMode.HALF_UP); // (a - b) / b

        // 转为百分比并返回
        return percentageChange.multiply(new BigDecimal(100));  // (result) * 100
    }

    private void createContractProductList(Long contractId, List<CrmContractProductVO> list) {
//        List<StoreProductAttrValueApiVO> storeProductAttrValueList = new ArrayList<>();
//        if (CollUtil.isNotEmpty(list)) {
//            List<Long> productIds = list.stream().map(CrmContractProductVO::getProductId).collect(Collectors.toList());
//            List<String> productAttrUnique = list.stream().map(CrmContractProductVO::getProductAttrUnique).collect(Collectors.toList());
//            storeProductAttrValueList = storeProductAttrValueApi.getStoreProductAttrValueList(new StoreProductAttrValueApiDTO()
//                    .setProductIds(productIds)
//                    .setUniques(productAttrUnique)).getCheckedData();
//        }
//        Map<String, List<StoreProductAttrValueApiVO>> map = storeProductAttrValueList.stream().collect(Collectors.groupingBy(a -> a.getProductId() + "_" + a.getUnique()));
//        list.forEach(o -> {
//            o.setContractId(contractId);
//            //库存处理
//            List<StoreProductAttrValueApiVO> storeProductAttrValueApiVOS = map.get(o.getProductId() + "_" + o.getProductAttrUnique());
//
//            int count = 0;
//            if (CollUtil.isNotEmpty(storeProductAttrValueApiVOS)) {
//                count = storeProductAttrValueApiVOS.stream().mapToInt(StoreProductAttrValueApiVO::getStock).sum();
//            }
//            if (NumberUtil.compare(count, o.getNums()) < 0) {
//                throw exception(new ErrorCode(202408250, "该商品ID：" + o.getProductId() + "库存不足"));
//            }
//            // TODO: 2024/11/21  这里的库存扣件完后 - 如果审批不通过 库存退回
//            this.decProductStock(o.getNums(), o.getProductId(), o.getProductAttrUnique());
//        });

        contractApi.createProduct(BeanUtils.toBean(list, CrmContractProductDTO.class));
    }

    /**
     * 减少库存
     *
     * @param num       数量
     * @param productId 产品ID
     * @param unique    sku唯一值
     */
    private void decProductStock(int num, Long productId, String unique) {
        String lockKey = productId + "_" + unique;
        // -- 分布式锁
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock(120 * SECOND_MILLIS, TimeUnit.MILLISECONDS);
            // 执行逻辑
            int res = 0;
            res = storeProductAttrValueApi.decStockIncSales(num, productId, unique).getCheckedData();

            if (res == 0) {
                throw exception(PRODUCT_STOCK_LESS);
            }

            int product = storeProductApi.decStockIncSales(num, productId).getCheckedData();
            if (product == 0) {
                throw exception(PRODUCT_STOCK_LESS);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 加库存
     *
     * @param num       数量
     * @param productId 产品ID
     * @param unique    sku唯一值
     */
    private void inProductStock(int num, Long productId, String unique) {
        String lockKey = productId + "_" + unique;
        // -- 分布式锁
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock(120 * SECOND_MILLIS, TimeUnit.MILLISECONDS);
            // 执行逻辑
            storeProductAttrValueApi.inStockIncSales(num, productId, unique).getCheckedData();
            storeProductApi.inStockIncSales(num, productId).getCheckedData();
        } finally {
            lock.unlock();
        }
    }
}
