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.planningguidedetail.ProjectPlanningGuideDetailDO;
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.planningguidedetail.ProjectPlanningGuideDetailMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper;
import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants;
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 java.util.Objects;

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_GUIDE_DETAIL_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_NOT_ALLOWED;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_REQUIRED;
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;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_QUARTER_SCOPE_MIXED;

/**
 * 季度分配 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;
    @Resource
    private ProjectPlanningGuideDetailMapper projectPlanningGuideDetailMapper;

    @Override
    public Long createProjectPlanningQuarter(ProjectPlanningQuarterSaveReqVO createReqVO) {
        ProjectPlanningDO planning = validatePlanningExists(createReqVO.getPlanningId());
        if (isZeroQuarter(createReqVO)) {
            return null;
        }
        ProjectPlanningGuideDetailDO guideDetail = validateGuideDetailScope(planning, createReqVO.getGuideDetailId());
        validateScopeNotMixed(null, createReqVO.getPlanningId(), guideDetail);
        validateQuarterUnique(null, createReqVO.getPlanningId(), createReqVO.getGuideDetailId(),
                createReqVO.getDistributionYear(), createReqVO.getQuarterNo());
        ProjectPlanningQuarterDO quarter = BeanUtils.toBean(createReqVO, ProjectPlanningQuarterDO.class);
        prepareQuarter(quarter, planning, guideDetail);
        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;
        }
        ProjectPlanningGuideDetailDO guideDetail = validateGuideDetailScope(planning, updateReqVO.getGuideDetailId());
        validateScopeNotMixed(updateReqVO.getId(), updateReqVO.getPlanningId(), guideDetail);
        validateQuarterUnique(updateReqVO.getId(), updateReqVO.getPlanningId(), updateReqVO.getGuideDetailId(),
                updateReqVO.getDistributionYear(), updateReqVO.getQuarterNo());
        ProjectPlanningQuarterDO updateObj = BeanUtils.toBean(updateReqVO, ProjectPlanningQuarterDO.class);
        prepareQuarter(updateObj, planning, guideDetail);
        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, Long guideDetailId,
                                       Integer distributionYear, Integer quarterNo) {
        ProjectPlanningQuarterDO quarter = guideDetailId == null
                ? projectPlanningQuarterMapper.selectParentByPlanningIdAndDistributionYearAndQuarter(
                        planningId, distributionYear, quarterNo)
                : projectPlanningQuarterMapper.selectByPlanningIdAndGuideDetailIdAndDistributionYearAndQuarter(
                        planningId, guideDetailId, 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 ProjectPlanningGuideDetailDO validateGuideDetailScope(ProjectPlanningDO planning, Long guideDetailId) {
        boolean guideDetailMode = ProjectPlanningBizTypeConstants.isMajorGuidanceScene(
                planning.getOwnershipType(), planning.getCalculationMethod());
        if (!guideDetailMode) {
            if (guideDetailId != null) {
                throw exception(PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_NOT_ALLOWED);
            }
            return null;
        }
        if (guideDetailId == null) {
            throw exception(PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_REQUIRED);
        }
        ProjectPlanningGuideDetailDO guideDetail = projectPlanningGuideDetailMapper.selectById(guideDetailId);
        if (guideDetail == null || !Objects.equals(guideDetail.getPlanningId(), planning.getId())) {
            throw exception(PROJECT_PLANNING_QUARTER_GUIDE_DETAIL_INVALID);
        }
        return guideDetail;
    }

    private void validateScopeNotMixed(Long id, Long planningId, ProjectPlanningGuideDetailDO guideDetail) {
        boolean savingParentQuarter = guideDetail == null;
        boolean mixed = savingParentQuarter
                ? projectPlanningQuarterMapper.existsGuideDetailQuarter(planningId, id)
                : projectPlanningQuarterMapper.existsParentQuarter(planningId, id);
        if (mixed) {
            throw exception(PROJECT_PLANNING_QUARTER_SCOPE_MIXED);
        }
    }

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

    private void prepareQuarter(ProjectPlanningQuarterDO quarter, ProjectPlanningDO planning,
                                ProjectPlanningGuideDetailDO guideDetail) {
        quarter.setDistributionRatio(ratio(quarter.getDistributionRatio()));
        if (guideDetail == null) {
            quarter.setGuideDetailId(null);
            quarter.setGuideDetailSortNo(null);
        } else {
            quarter.setGuideDetailId(guideDetail.getId());
            quarter.setGuideDetailSortNo(guideDetail.getSortNo());
        }
        BigDecimal assessmentOutputValue = guideDetail == null
                ? planning.getAssessmentOutputValue() : guideDetail.getAssessmentOutputValue();
        quarter.setDistributionAmount(multiplyAmount(
                assessmentOutputValue,
                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);
    }

}
