package com.dsk.cscec.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dsk.common.excel.ExcelUtils;
import com.dsk.common.exception.ServiceException;
import com.dsk.common.utils.StringUtils;
import com.dsk.cscec.constant.CbProjectConstants;
import com.dsk.cscec.domain.CbDirectExpense;
import com.dsk.cscec.domain.CbProjectFile;
import com.dsk.cscec.domain.CbProjectRecord;
import com.dsk.cscec.domain.bo.AddCbDirectExpenseBo;
import com.dsk.cscec.domain.vo.CbDirectExpenseDataDetailVo;
import com.dsk.cscec.domain.vo.CbDirectExpenseMenuVo;
import com.dsk.cscec.mapper.CbDirectExpenseMapper;
import com.dsk.cscec.mapper.CbProjectFileMapper;
import com.dsk.cscec.mapper.CbProjectRecordMapper;
import com.dsk.cscec.service.CbDirectExpenseService;
import com.dsk.system.service.ISysOssService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * 直接费成本表(CbDirectExpense)表服务实现类
 *
 * @author sxk
 * @since 2024-02-05 14:43:34
 */
@Service("cbDirectExpenseService")
public class CbDirectExpenseServiceImpl extends ServiceImpl<CbDirectExpenseMapper, CbDirectExpense> implements CbDirectExpenseService {
    @Resource
    private CbDirectExpenseMapper baseMapper;
    @Resource
    private CbProjectFileMapper projectFileMapper;
    @Resource
    private ISysOssService ossService;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private CbProjectRecordMapper projectRecordMapper;

    private final String regx = "^([1-9]|10)$";

    /**
     * 获取直接费菜单
     *
     * @param projectId 项目ID
     * @return 直接费菜单
     */
    @Override
    public CbDirectExpenseMenuVo getMenuList(Long projectId) {
        //校验项目是否存在
        this.checkProjectExist(projectId);

        //查询清单/费用项分类标识为数字的数字
        List<CbDirectExpenseMenuVo> menuVoList = baseMapper.selectMenuData(projectId, regx, CbProjectConstants.DELETE_FLAG_EXIST);
        Assert.isFalse(menuVoList.isEmpty(), "当前项目不存在直接费菜单");

        CbDirectExpenseMenuVo root = menuVoList.get(0);
        //生成菜单树形
        this.generateMenuTree(root, menuVoList);
        return root;
    }

    //生成菜单树形
    private void generateMenuTree(CbDirectExpenseMenuVo root, List<CbDirectExpenseMenuVo> menuVoList) {
        menuVoList.forEach(menu -> {
            if (Objects.equals(root.getMenuId(), menu.getParentId())) {
                root.getChildrenList().add(menu);
                generateMenuTree(menu, menuVoList);
            }
        });
    }

    /**
     * 直接费数据解析
     *
     * @param projectId 项目ID
     * @param cbStage   成本阶段
     */
    @Override
    public void parseDirectExpenseData(Long projectId, Integer cbStage) throws Exception {
        //查找该项目下所有相关文件
        List<CbProjectFile> projectFileList = projectFileMapper.selectAnalysisList(projectId, CbProjectConstants.CB_TYPE_DIRECT_EXPENSE, cbStage);
        if (ObjectUtils.isEmpty(projectFileList)) {
            return;
        }

        //处理待解析文件
        a:
        for (CbProjectFile projectFile : projectFileList) {
            //文件下载
            InputStream inputStream = ossService.downFileIO(projectFile.getFileOssId());
            if (ObjectUtil.isNull(inputStream)) {
                projectFile.setFileParseStatus(CbProjectConstants.PROJECT_FILE_STATUS_PARSE_FAIL);
                log.error("FileId:" + projectFile.getId() + ",文件数据不存在");
                projectFile.setFailRemark("文件数据不存在");
                projectFileMapper.updateById(projectFile);
                continue;
            }

            //解析数据
            List<CbDirectExpense> importList = new ExcelUtils<>(CbDirectExpense.class).importExcelAllSheet(inputStream, 0);
            if (importList.isEmpty()) {
                projectFile.setFileParseStatus(CbProjectConstants.PROJECT_FILE_STATUS_PARSE_FAIL);
                log.error("FileId:" + projectFile.getId() + ",表格中不存在待导入数据");
                projectFile.setFailRemark("表格中不存在待导入数据!");
                projectFileMapper.updateById(projectFile);
                continue;
            }

            //导入顺序标识
            AtomicReference<Integer> sort = new AtomicReference<>(1);
            //清单/费用项分类数字层级标识
            AtomicReference<Integer> numLevel = new AtomicReference<>(-1);
            //清单/费用项分类"清"层级标识
            AtomicReference<Integer> qingLevel = new AtomicReference<>(-1);
            //筛选有效数据
            List<CbDirectExpense> directExpenseList = importList.stream().parallel()
                    .filter(item -> StringUtils.isNotBlank(item.getExpenseName())
                            && StringUtils.isNotBlank(item.getExpenseCategoryTag()))
                    .collect(Collectors.toList());
            if (directExpenseList.size() != importList.size()) {
                projectFile.setFileParseStatus(CbProjectConstants.PROJECT_FILE_STATUS_PARSE_FAIL);
                log.error("FileId:" + projectFile.getId() + ",表格中\"名称\"列或\"清单/费用项分类标识\"列存在空数据");
                projectFile.setFailRemark("表格中\"名称\"列或\"清单/费用项分类标识\"列存在空数据");
                projectFileMapper.updateById(projectFile);
                continue;
            }

            //遍历数据集合,设置属性值
            for (int i = 0; i < directExpenseList.size(); i++) {
                CbDirectExpense item = directExpenseList.get(i);

                //设置导入顺序
                item.setSort(sort.get());
                //根据清单/费用项分类标识设置层级
                String expenseCategoryTag = item.getExpenseCategoryTag();
                expenseCategoryTag = expenseCategoryTag.trim();
                if (expenseCategoryTag.matches(regx)) {
                    //如果是数字,则层级为对应数字

                    int level = Integer.parseInt(expenseCategoryTag);
                    //数字层级只能为1-10级
                    if (level < 1 || level > 10) {
                        projectFile.setFileParseStatus(CbProjectConstants.PROJECT_FILE_STATUS_PARSE_FAIL);
                        log.error("FileId:" + projectFile.getId() + ",表格中\"清单/费用项分类标识\"列的数字只能为1-10");
                        projectFile.setFailRemark("表格中\"清单/费用项分类标识\"列的数字只能为1-10");
                        projectFileMapper.updateById(projectFile);
                        continue a;
                    }

                    numLevel.set(level);
                    item.setLevel(numLevel.get());
                } else if (CbProjectConstants.DIRECT_EXPENSE_CATEGORY_TAG_QING.equals(expenseCategoryTag)) {
                    //如果是清,则为倒数第二级,且清可以在任意数字层级下
                    qingLevel.set(numLevel.get() + 1);
                    item.setLevel(qingLevel.get());
                } else {
                    //如果是专、劳、材,则为最底级,且均为清的子集
                    item.setLevel(qingLevel.get() + 1);
                }

                //根据清单/费用项分类标识设置主键
                item.setId(IdUtil.getSnowflakeNextId());
                //防止雪花时间戳相同,小睡一下
                Thread.sleep(1);
                //设置父级ID
                if (i == 0) {
                    //如果是第一条数据,父级ID设置为0
                    item.setParentId(0L);
                } else {
                    //找到当前Level的最近一条上一级Level,对应的ID就是parentId
                    for (int j = i - 1; j >= 0; j--) {
                        //当前对象
                        CbDirectExpense currentLevelEntity = directExpenseList.get(i);
                        //上一级对象
                        CbDirectExpense preLevelEntity = directExpenseList.get(j);
                        if ((currentLevelEntity.getLevel() - 1) == preLevelEntity.getLevel()) {
                            currentLevelEntity.setParentId(preLevelEntity.getId());
                            break;
                        }
                    }
                }

                item.setProjectId(projectId);
                item.setProjectFileId(projectFile.getId());
                item.setIsImport(true);
                sort.getAndSet(sort.get() + 1);
            }

            transactionTemplate.execute(status -> {
                try {
                    CbDirectExpenseServiceImpl impl = new CbDirectExpenseServiceImpl();
                    //分批次插入
                    if (directExpenseList.size() > 1000) {
                        int index = 0;
                        int total = directExpenseList.size();
                        while (index < total) {
                            List<CbDirectExpense> divideList = directExpenseList.subList(index, Math.min(index += 1000, total));
                            Assert.isTrue(impl.saveBatch(divideList), "数据插入失败");
                        }
                    } else {
                        Assert.isTrue(impl.saveBatch(directExpenseList), "数据插入失败");
                    }
                    //更新文件状态
                    projectFile.setFileParseStatus(CbProjectConstants.PROJECT_FILE_STATUS_PARSE_SUCCESS);
                    //场景:若第一次解析失败了,用户未删除失败的文件,又解析一次,这时解析成功了,因此将失败备注置空
                    projectFile.setFailRemark(null);
                    Assert.isTrue(projectFileMapper.updateById(projectFile) > 0, "解析成功后文件状态更新失败");
                } catch (Exception e) {
                    status.setRollbackOnly();
                    projectFile.setFileParseStatus(CbProjectConstants.PROJECT_FILE_STATUS_PARSE_FAIL);
                    e.printStackTrace();
                    projectFile.setFailRemark(e.getMessage());
                    projectFileMapper.updateById(projectFile);
                }
                return Boolean.TRUE;
            });
        }
    }

    /**
     * 获取直接费数据详情
     *
     * @param menuId 菜单ID
     * @return 数据详情
     */
    @Override
    public List<CbDirectExpenseDataDetailVo> getDataDetail(Long menuId) {
        //查询该菜单下的清
        List<CbDirectExpenseDataDetailVo> qingList = BeanUtil.copyToList(baseMapper.selectList(new LambdaQueryWrapper<CbDirectExpense>()
                .eq(CbDirectExpense::getParentId, menuId)
                .eq(CbDirectExpense::getExpenseCategoryTag, CbProjectConstants.DIRECT_EXPENSE_CATEGORY_TAG_QING)), CbDirectExpenseDataDetailVo.class);
        Assert.isFalse(qingList.isEmpty(), "该菜单下无数据");

        //生成数据树形
        qingList.forEach(qing -> {
            qing.getChildrenList().addAll(BeanUtil.copyToList(baseMapper.selectList(new LambdaQueryWrapper<CbDirectExpense>()
                    .eq(CbDirectExpense::getParentId, qing.getId())), CbDirectExpenseDataDetailVo.class));
        });
        return qingList;
    }

    /**
     * 新增直接费费用项
     *
     * @param addBo 费用项
     * @return 新增结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addExpense(AddCbDirectExpenseBo addBo) {
        //校验清是否存在
        CbDirectExpense qing = this.checkExpenseExist(addBo.getQingId());
        CbDirectExpense directExpense = BeanUtil.toBean(addBo, CbDirectExpense.class);

        //排序设为清下面最后一条
        directExpense.setSort(baseMapper.selectOne(new LambdaQueryWrapper<CbDirectExpense>()
                .eq(CbDirectExpense::getParentId, qing.getId())
                .orderByDesc(CbDirectExpense::getSort)
                .last("LIMIT 1")).getSort() + 1);
        directExpense.setParentId(qing.getId());
        directExpense.setLevel(qing.getLevel() + 1);
        directExpense.setProjectId(qing.getProjectId());
        directExpense.setProjectFileId(-1L);
        directExpense.setIsImport(false);

        //取编码前两位字母,ZY代表专,LW代表劳,FG代表材
        String originCode = directExpense.getCode();
        Assert.isFalse(originCode.length() < 2, "编码长度不足");
        String code = originCode.substring(0, 2);
        switch (code) {
            case CbProjectConstants.DIRECT_EXPENSE_CODE_ZHUAN:
                directExpense.setExpenseCategoryTag(CbProjectConstants.DIRECT_EXPENSE_CATEGORY_TAG_ZHUAN);
                break;
            case CbProjectConstants.DIRECT_EXPENSE_CODE_LAO:
                directExpense.setExpenseCategoryTag(CbProjectConstants.DIRECT_EXPENSE_CATEGORY_TAG_LAO);
                break;
            case CbProjectConstants.DIRECT_EXPENSE_CODE_CAI:
                directExpense.setExpenseCategoryTag(CbProjectConstants.DIRECT_EXPENSE_CATEGORY_TAG_CAI);
                break;
            default:
                throw new ServiceException("编码错误");
        }

        return baseMapper.insert(directExpense);
    }


    /**
     * 删除直接费费用项
     *
     * @param id 费用ID
     * @return 删除结果
     */
    @Override
    public Integer deleteExpense(Long id) {
        //校验费用项是否存在
        CbDirectExpense directExpense = this.checkExpenseExist(id);
        Assert.isFalse(directExpense.getIsImport(), "不允许删除导入数据");

        return baseMapper.deleteById(id);
    }


    /**
     * 校验项目是否存在
     *
     * @param projectId 项目ID
     * @return 项目实体
     */
    private CbProjectRecord checkProjectExist(Long projectId) {
        CbProjectRecord projectRecord = projectRecordMapper.selectById(projectId);
        Assert.notNull(projectRecord, "该项目不存在");
        return projectRecord;
    }

    /**
     * 校验清是否存在
     *
     * @param id 费用ID
     * @return 数据实体
     */
    private CbDirectExpense checkExpenseExist(Long id) {
        CbDirectExpense directExpense = baseMapper.selectById(id);
        Assert.notNull(directExpense, "ID无效");
        return directExpense;
    }
}
