package cn.iocoder.yudao.module.crm.service.crmcontract;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.ShopCommonEnum;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.crm.controller.admin.crmcontract.vo.CheckInfoVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmcontract.vo.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmcontract.vo.CrmContractRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmcontract.vo.CrmContractSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmbusiness.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmcontract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmcontract.CrmContractProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmcustomer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmcustomercontacts.CrmCustomerContactsDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmflow.CrmFlowDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmflow.CrmFlowStepDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmflowlog.CrmFlowLogDO;
import cn.iocoder.yudao.module.crm.dal.mysql.crmbusiness.CrmBusinessMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmcontract.CrmContractMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmcontract.CrmContractProductMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmcustomer.CrmCustomerMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmcustomercontacts.CrmCustomerContactsMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmflow.CrmFlowMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmflow.CrmFlowStepMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmflowlog.CrmFlowLogMapper;
import cn.iocoder.yudao.module.crm.service.userlivetree.UserLiveTreeService;
import cn.iocoder.yudao.module.hrm.api.crmcontract.dto.CrmContractProductSettlementDTO;
import cn.iocoder.yudao.module.hrm.enums.ContractStatusEnum;
import cn.iocoder.yudao.module.hrm.enums.FlowStepEnum;
import cn.iocoder.yudao.module.hrm.enums.RelationEnum;
import cn.iocoder.yudao.module.hrm.enums.TypesEnum;
import cn.iocoder.yudao.module.product.api.storeproduct.StoreProductApi;
import cn.iocoder.yudao.module.product.api.storeproductattrvalue.StoreProductAttrValueApi;
import cn.iocoder.yudao.module.product.api.storeproductattrvalue.dto.StoreProductAttrValueApiDTO;
import cn.iocoder.yudao.module.product.api.storeproductattrvalue.vo.StoreProductAttrValueApiVO;
import cn.iocoder.yudao.module.product.api.storeproductattrvalue.vo.UserProductCountVO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserApiDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserApiVO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.api.workovertime.vo.WorkOvertimeRuleItemApiVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
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.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.SECOND_MILLIS;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.CONTRACT_NOT_EXISTS;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.FLOW_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.PRODUCT_STOCK_LESS;

/**
 * 合同 Service 实现类
 *
 * @author yshop
 */
@Service
@Validated
@Slf4j
public class CrmContractServiceImpl implements CrmContractService {

    @Resource
    private CrmContractMapper contractMapper;
    @Resource
    private CrmContractProductMapper contractProductMapper;
    @Resource
    private CrmCustomerMapper customerMapper;
    @Resource
    private CrmFlowMapper flowMapper;
    @Resource
    private CrmFlowStepMapper flowStepMapper;
    @Resource
    private CrmBusinessMapper crmBusinessMapper;
    @Resource
    private StoreProductAttrValueApi storeProductAttrValueApi;
    @Resource
    private StoreProductApi storeProductApi;
    @Resource
    private CrmFlowLogMapper crmFlowLogMapper;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private CrmCustomerContactsMapper crmCustomerContactsMapper;
    @Resource
    private UserLiveTreeService userLiveTreeService;

    private static final String LOCK_KEY = "contract:check:lock";


    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createContract(CrmContractSaveReqVO createReqVO) {

        //处理客户
        CrmCustomerDO crmCustomerDO = customerMapper.selectById(createReqVO.getCustomerId());

        // 插入
        CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class);
        contract.setOwnerUserId(SecurityFrameworkUtils.getLoginUserId());
        if (crmCustomerDO != null && crmCustomerDO.getSource() != null) {
            contract.setSourceId(crmCustomerDO.getSource());
        }
        contractMapper.insert(contract);

        // 插入子表
        createContractProductList(contract.getId(), createReqVO.getContractProducts());

        //更新审核
//        updateAfter(contract.getId());

        //插入日志
//        crmOperatelogService.createLog("添加合同", 0L, contract.getId(), 0L);

        //todo 站内信通知 审批

        // 返回
        return contract.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateContract(CrmContractSaveReqVO updateReqVO) {
        //处理客户
        CrmCustomerDO crmCustomerDO = customerMapper.selectById(updateReqVO.getCustomerId());
        // 校验存在
        validateContractExists(updateReqVO.getId());
        // 更新
        CrmContractDO updateObj = BeanUtils.toBean(updateReqVO, CrmContractDO.class);
        updateObj.setOwnerUserId(SecurityFrameworkUtils.getLoginUserId());
        if (crmCustomerDO != null && crmCustomerDO.getSource() != null) {
            updateObj.setSourceId(crmCustomerDO.getSource());
        }
        contractMapper.updateById(updateObj);

        //更新审核
        updateAfter(updateReqVO.getId());

        //todo 站内信通知 审批

        // 更新子表
        updateContractProductList(updateReqVO.getId(), updateReqVO.getContractProducts());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteContract(Long id) {
        // 校验存在
        CrmContractDO crmContractDO = validateContractExists(id);
        if (!ContractStatusEnum.STATUS_0.getValue().equals(crmContractDO.getCheckStatus())) {
            throw exception(new ErrorCode(202410101, "合同不是待审核状态不可以删除"));
        }
        // 删除
        contractMapper.deleteById(id);

        // 删除子表
        deleteContractProductByContractId(id);
    }

    private void updateAfter(Long id) {
        CrmFlowDO crmFlowDO = flowMapper.selectOne(new LambdaQueryWrapper<CrmFlowDO>()
                .eq(CrmFlowDO::getTypes, TypesEnum.CONTRACT.getValue())
                .orderByDesc(CrmFlowDO::getId)
                .last("limit 1"));
        CrmFlowStepDO crmFlowStepDO = flowStepMapper.selectOne(new LambdaQueryWrapper<CrmFlowStepDO>()
                .eq(CrmFlowStepDO::getFlowId, crmFlowDO.getId())
                .orderByAsc(CrmFlowStepDO::getId)
                .last("limit 1"));

        contractMapper.updateById(CrmContractDO.builder()
                .id(id)
                .stepId(crmFlowStepDO.getId())
                .flowId(crmFlowDO.getId()).build());
    }

    private CrmContractDO validateContractExists(Long id) {
        CrmContractDO crmContractDO = contractMapper.selectById(id);
        if (crmContractDO == null) {
            throw exception(CONTRACT_NOT_EXISTS);
        }

        return crmContractDO;
    }

    @Override
    public CrmContractRespVO getContract(Long id) {
        CrmContractDO crmContractDO = contractMapper.selectById(id);
        CrmContractRespVO crmContractRespVO = BeanUtils.toBean(crmContractDO, CrmContractRespVO.class);
        crmContractRespVO.setFlowAdminId2(crmContractDO.getFlowAdminId());
        CrmCustomerDO customerDO = customerMapper.selectById(crmContractDO.getCustomerId());
        crmContractRespVO.setCustomerName(customerDO.getName());
        if (crmContractDO.getBusinessId() != null && crmContractDO.getBusinessId() > 0) {
            CrmBusinessDO crmBusinessDO = crmBusinessMapper.selectById(crmContractDO.getBusinessId());
            crmContractRespVO.setBusinessName(crmBusinessDO.getName());
        }
        CrmCustomerContactsDO crmCustomerContactsDO = crmCustomerContactsMapper.selectById(crmContractDO.getContactsId());
        if (crmCustomerContactsDO != null) {
            crmContractRespVO.setContactsName(crmCustomerContactsDO.getName());
        }
        AdminUserRespDTO adminUserDO = adminUserApi.getUser(crmContractDO.getOrderAdminId()).getCheckedData();
        if (adminUserDO != null) {
            crmContractRespVO.setOrderAdminName(adminUserDO.getNickname());
        }
        return crmContractRespVO;
    }

    @Override
    public PageResult<CrmContractRespVO> getContractPage(CrmContractPageReqVO pageReqVO) {
        List<Long> ids = new ArrayList<>();
        Long loginAdminId = SecurityFrameworkUtils.getLoginUserId();
        if (RelationEnum.MY.getValue().equals(pageReqVO.getRelation())) {
            ids.add(loginAdminId);
        } else if (RelationEnum.SUB.getValue().equals(pageReqVO.getRelation())) {
            ids = userLiveTreeService.getItemIdsByUserId(loginAdminId);
        }
        pageReqVO.setLoginAdminId(loginAdminId);
        IPage mpPage = MyBatisUtils.buildPage(pageReqVO);
        IPage<CrmContractRespVO> pageResult = contractMapper.selectPageList(mpPage, pageReqVO, ids);
        for (CrmContractRespVO contractRespVO : pageResult.getRecords()) {
            List<String> adminIds = StrUtil.split(contractRespVO.getFlowAdminId(), ",");
            if (adminIds.contains(loginAdminId.toString())) {
                contractRespVO.setIsCheck(true);
            } else {
                contractRespVO.setIsCheck(false);
            }
        }
        return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
    }

    // ==================== 子表（合同产品关系） ====================

    @Override
    public List<CrmContractProductDO> getContractProductListByContractId(Long contractId) {
        List<CrmContractProductDO> crmContractProductDOS = contractProductMapper.selectListByContractId(contractId);
        List<StoreProductAttrValueApiVO> storeProductAttrValueList = new ArrayList<>();
        if (CollUtil.isNotEmpty(crmContractProductDOS)) {
            List<Long> productIds = crmContractProductDOS.stream().map(CrmContractProductDO::getProductId).collect(Collectors.toList());
            List<String> productAttrUnique = crmContractProductDOS.stream().map(CrmContractProductDO::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()));

        for (CrmContractProductDO contractProductDO : crmContractProductDOS) {
            List<StoreProductAttrValueApiVO> storeProductAttrValueApiVOS = map.get(contractProductDO.getProductId() + "_" + contractProductDO.getProductAttrUnique());
            if (CollUtil.isNotEmpty(storeProductAttrValueApiVOS)) {
                contractProductDO.setSku(storeProductAttrValueApiVOS.get(0).getSku());
            }
        }
        return crmContractProductDOS;
    }


    @Override
    public String getCode() {
        CrmContractDO crmContractDO = contractMapper
                .selectOne(new LambdaQueryWrapper<CrmContractDO>()
                        .orderByDesc(CrmContractDO::getNumber).last("limit 1"));
        String format = DateUtil.format(new Date(), "yyyyMMdd");
        if (crmContractDO == null) {
            return "HT" + format + "1000";
        }
        List<String> numbers = ReUtil.findAll(PatternPool.NUMBERS, crmContractDO.getNumber(), 0);
        String number = format + "1000";
        if (!numbers.isEmpty()) {
            number = numbers.get(0);
        }
        BigDecimal newNumber = new BigDecimal(number).add(BigDecimal.ONE);
        return "HT" + newNumber;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void check(CheckInfoVO checkInfoVO) {
        CrmContractDO crmContractDO = contractMapper.selectById(checkInfoVO.getId());
        if (crmContractDO == null) {
            throw exception(CONTRACT_NOT_EXISTS);
        }
        CrmFlowDO crmFlowDO = flowMapper.selectById(crmContractDO.getFlowId());
        if (crmFlowDO == null) {
            throw exception(FLOW_NOT_EXISTS);
        }
        Long loginAdminId = SecurityFrameworkUtils.getLoginUserId();
        if (!crmContractDO.getFlowAdminId().contains(loginAdminId)) {
            throw exception(new ErrorCode(202408111, "无权限审核"));
        }
        if (crmContractDO.getCheckStatus() >= ContractStatusEnum.STATUS_2.getValue()) {
            throw exception(new ErrorCode(202408110, "已经审核过"));
        }

        //组合审批日志
        CrmFlowLogDO crmFlowLogDO = CrmFlowLogDO.builder()
                .flowId(crmContractDO.getFlowId())
                .types(TypesEnum.CONTRACT.getValue())
                .typesId(crmContractDO.getId())
                .adminId(SecurityFrameworkUtils.getLoginUserId())
                .nickname(SecurityFrameworkUtils.getLoginUserNickname())
                .build();

        RLock lock = redissonClient.getLock(LOCK_KEY);
        if (lock.tryLock()) {
            //加锁防止并发审核
            try {
                //查询审核步骤
                Long count = flowStepMapper.selectCount(new LambdaQueryWrapper<CrmFlowStepDO>()
                        .eq(CrmFlowStepDO::getFlowId, crmContractDO.getFlowId()));
                //加入已经审核人
                String checkAdminId = StrUtil.isEmpty(crmContractDO.getCheckAdminId()) ? loginAdminId.toString() : crmContractDO.getCheckAdminId() + "," + loginAdminId;
                crmContractDO.setCheckAdminId(checkAdminId);
                if (ShopCommonEnum.AGREE_1.getValue().equals(checkInfoVO.getAgreeType())) {
                    //查询当前合同处于那一步骤
                    CrmFlowStepDO crmFlowStepDO = flowStepMapper.selectById(crmContractDO.getStepId());
                    if (crmFlowStepDO.getRelation() < count) {
                        crmContractDO.setCheckStatus(ContractStatusEnum.STATUS_1.getValue());
                        //判断当前步骤是不是最后一个人,如果是 开始审核后开始存储下一步信息
                        if (crmContractDO.getFlowAdminId().size() == 1) {
                            List<Long> list = new ArrayList<>();
                            CrmFlowStepDO next = flowStepMapper.selectOne(new LambdaQueryWrapper<CrmFlowStepDO>()
                                    .eq(CrmFlowStepDO::getFlowId, crmContractDO.getFlowId()).eq(CrmFlowStepDO::getRelation, crmFlowStepDO.getRelation() + 1));
                            if (FlowStepEnum.TYPE_3.getValue().equals(next.getType())) {
                                List<AdminUserApiVO> adminUserDOS = adminUserApi.getUserList(new AdminUserApiDTO().setDeptId(next.getAdminIds()).setNextDept(1)).getCheckedData();
                                list = adminUserDOS.stream().map(AdminUserApiVO::getId).collect(Collectors.toList());
                            } else {
                                list.add(next.getAdminIds());
                            }
                            crmContractDO.setFlowAdminId(list);
                            crmContractDO.setStepId(next.getId());
                        } else {
                            //移除已经审核等人
                            List<Long> nowAdminId = crmContractDO.getFlowAdminId();
                            nowAdminId.remove(loginAdminId);
                            crmContractDO.setFlowAdminId(nowAdminId);
                        }

                    } else {
                        //步骤走完
                        crmContractDO.setCheckStatus(ContractStatusEnum.STATUS_2.getValue());
                        crmContractDO.setFlowAdminId(new ArrayList<>());
                        crmFlowLogDO.setIsEnd(ShopCommonEnum.IS_STATUS_1.getValue());//审核结束

                        //客户成交
                        CrmCustomerDO customerDO = customerMapper.selectById(crmContractDO.getCustomerId());
                        customerDO.setDealStatus(ShopCommonEnum.IS_STATUS_1.getValue());
                        customerDO.setDealTime(LocalDateTime.now());
                        customerDO.setPurchaseTimes(customerDO.getPurchaseTimes() + 1);
                        customerDO.setPurchaseTotal(customerDO.getPurchaseTotal().add(crmContractDO.getMoney()));
                        customerMapper.updateById(customerDO);

                    }
                    crmFlowLogDO.setStatus(ShopCommonEnum.IS_STATUS_1.getValue());//审核通过
                } else {
                    //拒绝
                    crmContractDO.setCheckStatus(ContractStatusEnum.STATUS_3.getValue());
                    crmContractDO.setCheckAdminId("");
                    crmContractDO.setFlowAdminId(new ArrayList<>());
                    crmFlowLogDO.setRemark(checkInfoVO.getRemark());
                }
                contractMapper.updateById(crmContractDO);
                //存入审批日志
                crmFlowLogMapper.insert(crmFlowLogDO);
            } catch (Exception ex) {
                log.error("[message][执行异常]", ex);
            } finally {
                lock.unlock();
            }
        }

    }

    @Override
    public void createProduct(List<CrmContractProductDO> createReqVO) {

        contractProductMapper.insertBatch(createReqVO);
    }

    @Override
    public List<UserProductCountVO> getProductCount(List<Long> userIds, LocalDateTime[] createTime) {
        return contractMapper.getProductCount(userIds, createTime);
    }

    @Override
    public List<CrmContractProductSettlementDTO> getContractProductSettlement() {
        return contractMapper.getContractProductSettlement();
    }

    private void createContractProductList(Long contractId, List<CrmContractProductDO> list) {
//        List<StoreProductAttrValueApiVO> storeProductAttrValueList = new ArrayList<>();
//        if (CollUtil.isNotEmpty(list)) {
//            List<Long> productIds = list.stream().map(CrmContractProductDO::getProductId).collect(Collectors.toList());
//            List<String> productAttrUnique = list.stream().map(CrmContractProductDO::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());
//        });

        contractProductMapper.insertBatch(list);
    }

    /**
     * 减少库存
     *
     * @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();
        }
    }

    private void updateContractProductList(Long contractId, List<CrmContractProductDO> list) {
        deleteContractProductByContractId(contractId);
        list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下：1）id 冲突；2）updateTime 不更新
        createContractProductList(contractId, list);
    }

    private void deleteContractProductByContractId(Long contractId) {
        contractProductMapper.deleteByContractId(contractId);
    }

}
