package com.jeeplus.psimanage.loss.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jeeplus.common.TokenProvider;
import com.jeeplus.core.query.QueryWrapperGenerator;
import com.jeeplus.flowable.feign.IFlowableApi;
import com.jeeplus.psimanage.collect.domain.PsiCollectDetailedRecord;
import com.jeeplus.psimanage.collect.service.dto.PsiCollectDto;
import com.jeeplus.psimanage.collect.service.dto.PsiCollectExportDto;
import com.jeeplus.psimanage.loss.domain.PsiLossBasic;
import com.jeeplus.psimanage.loss.domain.PsiLossDetailed;
import com.jeeplus.psimanage.loss.mapper.PsiLossBasicMapper;
import com.jeeplus.psimanage.loss.mapper.PsiLossDetailedMapper;
import com.jeeplus.psimanage.loss.service.dto.PsiLossDto;
import com.jeeplus.psimanage.loss.service.dto.PsiLossExportDto;
import com.jeeplus.psimanage.oss.service.dto.WorkAttachmentDto;
import com.jeeplus.psimanage.psiWareHouse.domain.PsiWareHouseDetailed;
import com.jeeplus.psimanage.psiWareHouse.mapper.PsiWareHouseDetailedMapper;
import com.jeeplus.psimanage.psiWareHouse.mapper.PsiWareHouseSummaryMapper;
import com.jeeplus.psimanage.purchase.service.PsiMaterialService;
import com.jeeplus.psimanage.serialNumTpl.service.PsiSerialnumTplService;
import com.jeeplus.sys.feign.IRoleApi;
import com.jeeplus.sys.feign.IUserApi;
import com.jeeplus.sys.feign.IWorkAttachmentApi;
import com.jeeplus.sys.service.dto.RoleDTO;
import com.jeeplus.sys.service.dto.UserDTO;
import com.jeeplus.utils.StringUtils;
import org.springframework.beans.BeanUtils;
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.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class PsiLossService {

    @Resource
    private PsiLossBasicMapper basicMapper;

    @Resource
    private PsiLossDetailedMapper detailedMapper;

    @Resource
    private PsiSerialnumTplService psiSerialnumTplService;

    @Resource
    private PsiMaterialService psiMaterialService;

    @Resource
    private PsiWareHouseSummaryMapper summaryMapper;

    @Resource
    private PsiWareHouseDetailedMapper psiWareHouseDetailedMapper;

    @Resource
    private IFlowableApi flowTaskService;

    public void updateStatusById(PsiLossDto dto) {
        basicMapper.updateStatusById(dto.getId(), dto.getStatus());
    }

    public String remove(String id) {
        basicMapper.deleteById(id);
        List<PsiLossDetailed> detailList = detailedMapper.getByBasicId(id);
        if (detailList != null) {
            detailList.forEach(detail -> {
                SpringUtil.getBean(IWorkAttachmentApi.class).deleteByAttachmentId(detail.getId());
                detailedMapper.deleteById(detail.getId());
            });
        }
        SpringUtil.getBean(IWorkAttachmentApi.class).deleteByAttachmentId(id);
        return "操作成功";
    }

    public String save(PsiLossDto dto) throws Exception {
        UserDTO userDTO = SpringUtil.getBean(IUserApi.class).getByToken(TokenProvider.getCurrentToken());
        synchronized (this) {
            if (StringUtils.isNotBlank(dto.getId()) && !"false".equals(dto.getId())) {
                return update(dto, userDTO);
            }
            return add(dto, userDTO);
        }
    }

    public String add(PsiLossDto dto, UserDTO userDTO) throws Exception {
        validateDetailInfos(dto.getDetailInfos());
        PsiLossDetailed detail = dto.getDetailInfos().get(0);
        prepareDetail(detail, userDTO);
        fillStockInfo(detail);

        String id = UUID.randomUUID().toString().replace("-", "");
        PsiLossBasic info = new PsiLossBasic();
        BeanUtils.copyProperties(dto, info);
        info.setId(id);
        info.setLossNo(buildLossNo(dto, userDTO));
        info.setHandledBy(dto.getHandledById());
        info.setCreateById(userDTO.getId());
        info.setCreateTime(new Date());
        info.setUpdateById(userDTO.getId());
        info.setUpdateTime(new Date());
        info.setDelFlag(0);
        basicMapper.insert(info);

        detail.setId(UUID.randomUUID().toString().replace("-", ""));
        detail.setBasicId(id);
        detailedMapper.insert(detail);
        saveDetailFiles(detail, userDTO);

        if (CollectionUtils.isNotEmpty(dto.getFiles())) {
            psiMaterialService.saveFiles(dto.getFiles(), userDTO, id);
        }

        if (isApproved(info.getStatus())) {
            deductInventory(detail);
        }
        return id;
    }

    public String update(PsiLossDto dto, UserDTO userDTO) throws Exception {
        validateDetailInfos(dto.getDetailInfos());
        PsiLossBasic oldInfo = basicMapper.selectById(dto.getId());
        if (oldInfo == null) {
            throw new RuntimeException("报损单不存在");
        }

        PsiLossBasic info = new PsiLossBasic();
        BeanUtils.copyProperties(dto, info);
        info.setId(dto.getId());
        info.setHandledBy(dto.getHandledById());
        info.setUpdateById(userDTO.getId());
        info.setUpdateTime(new Date());
        basicMapper.updateById(info);

        List<String> dbIds = detailedMapper.getIdByBasicId(dto.getId());
        List<String> liveIds = new ArrayList<>();
        PsiLossDetailed detail = dto.getDetailInfos().get(0);
        prepareDetail(detail, userDTO);
        fillStockInfo(detail);
        detail.setBasicId(dto.getId());

        if (StringUtils.isNotBlank(detail.getId())) {
            liveIds.add(detail.getId());
        }
        if (CollectionUtils.isNotEmpty(dbIds)) {
            dbIds.stream()
                    .filter(dbId -> !liveIds.contains(dbId))
                    .forEach(deleteId -> {
                        SpringUtil.getBean(IWorkAttachmentApi.class).deleteByAttachmentId(deleteId);
                        detailedMapper.deleteById(deleteId);
                    });
        }

        if (StringUtils.isBlank(detail.getId()) || detailedMapper.selectById(detail.getId()) == null) {
            detail.setId(UUID.randomUUID().toString().replace("-", ""));
            detail.setCreateById(userDTO.getId());
            detail.setCreateTime(new Date());
            detail.setDelFlag(0);
            detailedMapper.insert(detail);
            saveDetailFiles(detail, userDTO);
        } else {
            detailedMapper.updateById(detail);
            psiMaterialService.updateFiles(detail.getFileInfoLost(), userDTO, detail.getId());
        }

        psiMaterialService.updateFiles(dto.getFiles(), userDTO, dto.getId());

        if (!isApproved(oldInfo.getStatus()) && isApproved(info.getStatus())) {
            deductInventory(detail);
            //通知发起人
            List<UserDTO> notifiedPartyUsers = new ArrayList<>();
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            String day = format.format(new Date());
            String title = "[报损编号："+dto.getLossNo()+"]-报损申请审批通过";
            UserDTO initiator = SpringUtil.getBean(IUserApi.class).getById(dto.getHandledById());
            notifiedPartyUsers.add(initiator);
            //向用户发起通知
            if (null != notifiedPartyUsers) {
                for (UserDTO userDTO1 : notifiedPartyUsers) {
                    //发送通知
                    Map<String ,String > map = new HashMap<>();
                    map.put("taskId",dto.getProcInsId());
                    map.put("title",title);
                    map.put("defId",dto.getProcessDefinitionId());
                    map.put("taskName","报损申请审批通过");
                    map.put("createUser","管理员");
                    map.put("createTime",day);
                    map.put("noticeName",userDTO1.getLoginName());
                    map.put("noticeId",userDTO1.getId());
                    map.put("createById",userDTO.getId());
                    flowTaskService.add(map);
                }
            }
        }


        return dto.getId();
    }

    public PsiLossDto findById(String id) throws Exception {
        PsiLossDto dto = new PsiLossDto();
        PsiLossBasic info = basicMapper.selectById(id);
        if (info == null) {
            return dto;
        }
        BeanUtils.copyProperties(info, dto);
        List<PsiLossDetailed> detailList = detailedMapper.getByBasicId(id);
        if (CollectionUtils.isNotEmpty(detailList)) {
            detailList.forEach(detail -> {
                detail.setSurplusNumber(formatNumber(getAvailableBottleCount(detail.getGoodsName(), getCollectTypeId(detail))));
                List<WorkAttachmentDto> fileList = detailedMapper.getByAttachmentId(detail.getId());
                if (CollectionUtils.isNotEmpty(fileList)) {
                    fileList.forEach(file -> file.setCreateBy(SpringUtil.getBean(IUserApi.class).getById(file.getBy())));
                }
                detail.setFileInfoLost(fileList);
            });
        }
        dto.setDetailInfos(detailList);

        List<WorkAttachmentDto> files = detailedMapper.getByAttachmentId(info.getId());
        if (CollectionUtils.isNotEmpty(files)) {
            files.forEach(file -> file.setCreateBy(SpringUtil.getBean(IUserApi.class).getById(file.getBy())));
        }
        dto.setFiles(files);
        dto.setHandledById(info.getHandledBy());
        dto.setHandledBy(basicMapper.getUserNameByUserId(info.getHandledBy()));
        dto.setHandledByOfficeName(basicMapper.getOfficeNameByOfficeId(info.getHandledByOffice()));
        return dto;
    }

    public IPage<PsiLossDto> list(Page<PsiLossDto> page, PsiLossDto dto) throws Exception {
        QueryWrapper<PsiLossDto> queryWrapper = QueryWrapperGenerator.buildQueryCondition(dto, PsiLossDto.class);
        queryWrapper.eq("a.del_flag", "0");
        if (StringUtils.isNotBlank(dto.getLossNo())) {
            queryWrapper.like("a.collect_no", dto.getLossNo());
        }
        if (StringUtils.isNotBlank(dto.getGoodsName())) {
            queryWrapper.like("b.goods_name", dto.getGoodsName());
        }
        if (StringUtils.isNotBlank(dto.getHandledBy())) {
            queryWrapper.eq("su.id", dto.getHandledBy());
        }
        if (StringUtils.isNotBlank(dto.getHandledByOffice())) {
            List<String> childIds = basicMapper.findChildIds(dto.getHandledByOffice());
            if (childIds != null && !childIds.isEmpty()) {
                childIds.add(dto.getHandledByOffice());
                queryWrapper.in("a.handled_by_office", childIds);
            } else {
                queryWrapper.eq("a.handled_by_office", dto.getHandledByOffice());
            }
        }
        if (dto.getLossDates() != null && dto.getLossDates().length == 2) {
            queryWrapper.between("a.collect_date", dto.getLossDates()[0], dto.getLossDates()[1]);
        }
        IPage<PsiLossDto> pageData = basicMapper.findList(page, queryWrapper);
        pageData.getRecords().forEach(item -> {
            if ("2".equals(item.getStatus()) && StringUtils.isNotBlank(item.getTaskId())) {
                item.setAuditUserIds(flowTaskService.getTaskAuditUsers(item.getTaskId()));
            }
        });
        return pageData;
    }

    /**
     * 根据库存名称查询报损数据
     * @param page
     * @param dto
     * @return
     * @throws Exception
     */
    public IPage<PsiLossDetailed> getLossByTradeName(Page<PsiLossDetailed> page, PsiLossDetailed dto) throws Exception {
        QueryWrapper<PsiLossDetailed> queryWrapper = QueryWrapperGenerator.buildQueryCondition(dto, PsiLossDetailed.class);
        queryWrapper.eq("a.del_flag", "0");
        queryWrapper.eq("b.status", "5");
        if (StringUtils.isNotBlank(dto.getLossNo())) {
            queryWrapper.like("b.collect_no", dto.getLossNo());
        }
        if (StringUtils.isNotBlank(dto.getGoodsName())) {
            queryWrapper.eq("a.goods_name", dto.getGoodsName());
        }
        if (dto.getLossDates() != null && dto.getLossDates().length == 2) {
            queryWrapper.between("b.collect_date", dto.getLossDates()[0], dto.getLossDates()[1]);
        }

        IPage<PsiLossDetailed> pageData = basicMapper.getLossByTradeName(page, queryWrapper);
        return pageData;
    }

    private void validateDetailInfos(List<PsiLossDetailed> detailInfos) {
        if (CollectionUtils.isEmpty(detailInfos)) {
            throw new RuntimeException("报损明细不能为空");
        }
        if (detailInfos.size() != 1) {
            throw new RuntimeException("报损单只能保存一条明细");
        }
        PsiLossDetailed detail = detailInfos.get(0);
        if (StringUtils.isBlank(detail.getRecipientAgentId())) {
            throw new RuntimeException("报损人不能为空");
        }
        if (StringUtils.isBlank(getCollectTypeId(detail))) {
            throw new RuntimeException("报损类型不能为空");
        }
        if (StringUtils.isBlank(detail.getGoodsName())) {
            throw new RuntimeException("物品名称不能为空");
        }
        if (StringUtils.isBlank(detail.getLossNumber())) {
            throw new RuntimeException("报损数量不能为空");
        }
    }

    private void prepareDetail(PsiLossDetailed detail, UserDTO userDTO) {
        detail.setRecipientAgent(detail.getRecipientAgentId());
        detail.setCollectType(getCollectTypeId(detail));
        detail.setUpdateById(userDTO.getId());
        detail.setUpdateTime(new Date());
        if (StringUtils.isBlank(detail.getId())) {
            detail.setCreateById(userDTO.getId());
            detail.setCreateTime(new Date());
            detail.setDelFlag(0);
        }
    }

    private void saveDetailFiles(PsiLossDetailed detail, UserDTO userDTO) throws Exception {
        if (CollectionUtils.isNotEmpty(detail.getFileInfoLost())) {
            psiMaterialService.saveFiles(detail.getFileInfoLost(), userDTO, detail.getId());
        }
    }

    private void fillStockInfo(PsiLossDetailed detail) {
        detail.setSurplusNumber(formatNumber(getAvailableBottleCount(detail.getGoodsName(), getCollectTypeId(detail))));
    }

    private void deductInventory(PsiLossDetailed detail) {
        double lossNumber = parseDouble(detail.getLossNumber());
        double available = getAvailableBottleCount(detail.getGoodsName(), getCollectTypeId(detail));
        if (lossNumber - available > 0.001D) {
            throw new RuntimeException("报损数量不能大于当前库存数量");
        }
        List<PsiWareHouseDetailed> updateList = batchCalculation(detail);
        if (CollUtil.isNotEmpty(updateList)) {
            psiWareHouseDetailedMapper.updateBatchById(updateList);
        }
    }

    private List<PsiWareHouseDetailed> batchCalculation(PsiLossDetailed detail) {
        List<PsiWareHouseDetailed> inventoryList = summaryMapper.getInfoByProduceDateDesc(detail.getGoodsName(), getCollectTypeId(detail));
        List<PsiWareHouseDetailed> updateList = new ArrayList<>();
        double pending = parseDouble(detail.getLossNumber());
        for (PsiWareHouseDetailed inventory : inventoryList) {
            if (pending <= 0.001D) {
                break;
            }
            double currentBox = parseDouble(inventory.getCurrentInventory());
            double spec = parseSpec(inventory.getSpec());
            double totalUnit = round(currentBox * spec);
            if (totalUnit <= 0.001D) {
                continue;
            }
            PsiWareHouseDetailed update = new PsiWareHouseDetailed();
            update.setId(inventory.getId());
            if (totalUnit >= pending) {
                update.setCurrentInventory(formatNumber(round((totalUnit - pending) / spec)));
                pending = 0D;
            } else {
                update.setCurrentInventory("0");
                pending = round(pending - totalUnit);
            }
            updateList.add(update);
        }
        return updateList;
    }

    private double getAvailableBottleCount(String goodsName, String collectTypeId) {
        if (StringUtils.isBlank(goodsName) || StringUtils.isBlank(collectTypeId)) {
            return 0D;
        }
        List<PsiWareHouseDetailed> inventoryList = summaryMapper.getInfoByProduceDateDesc(goodsName, collectTypeId);
        double total = 0D;
        for (PsiWareHouseDetailed inventory : inventoryList) {
            total += parseDouble(inventory.getCurrentInventory()) * parseSpec(inventory.getSpec());
        }
        return round(total);
    }

    private String getCollectTypeId(PsiLossDetailed detail) {
        return StringUtils.isNotBlank(detail.getCollectTypeId()) ? detail.getCollectTypeId() : detail.getCollectType();
    }

    private String buildLossNo(PsiLossDto dto, UserDTO userDTO) {
        try {
            return psiSerialnumTplService.genSerialNum(userDTO.getCompanyDTO().getId(), PsiLossDto.BIZ_CODE, TokenProvider.getCurrentToken());
        } catch (Exception ex) {
            return "BS" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        }
    }

    private boolean isApproved(String status) {
        return "5".equals(status);
    }

    private double parseDouble(String value) {
        if (StringUtils.isBlank(value)) {
            return 0D;
        }
        return Double.parseDouble(value);
    }

    private double parseSpec(String spec) {
        double parsed = parseDouble(spec);
        return parsed <= 0 ? 1D : parsed;
    }

    private double round(double value) {
        return BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP).doubleValue();
    }

    private String formatNumber(double value) {
        return BigDecimal.valueOf(value).stripTrailingZeros().toPlainString();
    }


    public List<PsiLossExportDto> exportRecordList(Page<PsiLossDto> page , PsiLossDto dto) throws Exception{
        List<PsiLossDto> basicList = this.list(page, dto).getRecords();
        List<PsiLossExportDto> result = new ArrayList<>();
        if (CollectionUtils.isEmpty(basicList)) {
            return result;
        }
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        basicList.forEach(item -> {
            List<PsiLossDetailed> recordList = detailedMapper.getByBasicId(item.getId());
            if (CollectionUtils.isEmpty(recordList)) {
                return;
            }
            recordList.forEach(record -> {
                PsiLossExportDto exportDto = new PsiLossExportDto();
                exportDto.setLossNo(item.getLossNo());
                exportDto.setRecipientAgent(record.getRecipientAgent());
                exportDto.setRecipientOffice(record.getRecipientOffice());
                exportDto.setCollectType(record.getCollectType());
                exportDto.setGoodsName(record.getGoodsName());
                exportDto.setProduceDate(record.getProduceDate() == null ? "暂无生产日期" : dateFormat.format(record.getProduceDate()));
                exportDto.setLossNumber(record.getLossNumber());
                result.add(exportDto);
            });
        });
        return result;
    }
}
