package cn.iocoder.lyzsys.module.tjt.service.planningquarter;

import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;

import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_DUPLICATE;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_NOT_EXISTS;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_PLANNING_NOT_EXISTS;

/**
 * 季度分配 Service 实现类
 *
 * @author Codex
 */
@Service
@Validated
public class ProjectPlanningQuarterServiceImpl implements ProjectPlanningQuarterService {

    private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(4, RoundingMode.HALF_UP);

    @Resource
    private ProjectPlanningQuarterMapper projectPlanningQuarterMapper;
    @Resource
    private ProjectPlanningMapper projectPlanningMapper;

    @Override
    public Long createProjectPlanningQuarter(ProjectPlanningQuarterSaveReqVO createReqVO) {
        ProjectPlanningDO planning = validatePlanningExists(createReqVO.getPlanningId());
        if (isZeroQuarter(createReqVO)) {
            return null;
        }
        validateQuarterUnique(null, createReqVO.getPlanningId(),
                createReqVO.getDistributionYear(), createReqVO.getQuarterNo());
        ProjectPlanningQuarterDO quarter = BeanUtils.toBean(createReqVO, ProjectPlanningQuarterDO.class);
        prepareQuarter(quarter, planning);
        projectPlanningQuarterMapper.insert(quarter);
        return quarter.getId();
    }

    @Override
    public void updateProjectPlanningQuarter(ProjectPlanningQuarterSaveReqVO updateReqVO) {
        ProjectPlanningQuarterDO dbQuarter = validateQuarterExists(updateReqVO.getId());
        ProjectPlanningDO planning = validatePlanningExists(updateReqVO.getPlanningId());
        if (isZeroQuarter(updateReqVO)) {
            projectPlanningQuarterMapper.deleteById(dbQuarter.getId());
            return;
        }
        validateQuarterUnique(updateReqVO.getId(), updateReqVO.getPlanningId(),
                updateReqVO.getDistributionYear(), updateReqVO.getQuarterNo());
        ProjectPlanningQuarterDO updateObj = BeanUtils.toBean(updateReqVO, ProjectPlanningQuarterDO.class);
        prepareQuarter(updateObj, planning);
        projectPlanningQuarterMapper.updateById(updateObj);
    }

    @Override
    public void deleteProjectPlanningQuarter(Long id) {
        validateQuarterExists(id);
        projectPlanningQuarterMapper.deleteById(id);
    }

    @Override
    public void deleteProjectPlanningQuarterList(List<Long> ids) {
        ids.forEach(this::validateQuarterExists);
        projectPlanningQuarterMapper.deleteBatchIds(ids);
    }

    @Override
    public ProjectPlanningQuarterDO getProjectPlanningQuarter(Long id) {
        return projectPlanningQuarterMapper.selectById(id);
    }

    @Override
    public List<ProjectPlanningQuarterDO> getProjectPlanningQuarterListByPlanningId(Long planningId) {
        validatePlanningExists(planningId);
        return projectPlanningQuarterMapper.selectListByPlanningId(planningId);
    }

    private void validateQuarterUnique(Long id, Long planningId, Integer distributionYear, Integer quarterNo) {
        ProjectPlanningQuarterDO quarter = projectPlanningQuarterMapper.selectByPlanningIdAndDistributionYearAndQuarter(
                planningId, distributionYear, quarterNo);
        if (quarter == null) {
            return;
        }
        if (id == null || !quarter.getId().equals(id)) {
            throw exception(PROJECT_PLANNING_QUARTER_DUPLICATE);
        }
    }

    private ProjectPlanningDO validatePlanningExists(Long planningId) {
        ProjectPlanningDO planning = projectPlanningMapper.selectById(planningId);
        if (planning == null) {
            throw exception(PROJECT_PLANNING_QUARTER_PLANNING_NOT_EXISTS);
        }
        return planning;
    }

    private ProjectPlanningQuarterDO validateQuarterExists(Long id) {
        ProjectPlanningQuarterDO quarter = projectPlanningQuarterMapper.selectById(id);
        if (quarter == null) {
            throw exception(PROJECT_PLANNING_QUARTER_NOT_EXISTS);
        }
        return quarter;
    }

    private boolean isZeroQuarter(ProjectPlanningQuarterSaveReqVO reqVO) {
        return ratio(reqVO.getDistributionRatio()).compareTo(BigDecimal.ZERO) == 0;
    }

    private void prepareQuarter(ProjectPlanningQuarterDO quarter, ProjectPlanningDO planning) {
        quarter.setDistributionRatio(ratio(quarter.getDistributionRatio()));
        quarter.setDistributionAmount(multiplyAmount(
                planning.getAssessmentOutputValue(),
                planning.getTotalDistributionAmount(),
                quarter.getDistributionRatio()));
    }

    private BigDecimal amount(BigDecimal value) {
        return value == null ? ZERO_AMOUNT : value.setScale(2, RoundingMode.HALF_UP);
    }

    private BigDecimal ratio(BigDecimal value) {
        return value == null ? ZERO_RATIO : value.setScale(4, RoundingMode.HALF_UP);
    }

    private BigDecimal multiplyAmount(BigDecimal... values) {
        BigDecimal result = BigDecimal.ONE;
        for (BigDecimal value : values) {
            result = result.multiply(value == null ? BigDecimal.ZERO : value);
        }
        return result.setScale(2, RoundingMode.HALF_UP);
    }

}
