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

import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.projectroleperson.ProjectRolePersonDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.outputsplit.ProjectOutputSplitMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.projectroleperson.ProjectRolePersonMapper;
import cn.iocoder.lyzsys.module.tjt.enums.OutputSplitBizConstants;
import org.springframework.dao.DuplicateKeyException;
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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_OUTPUT_SPLIT_PLANNING_NOT_EXISTS;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_OUTPUT_SPLIT_RATIO_INVALID;

/**
 * 页面4拆分比例 Service 实现类
 *
 * @author Codex
 */
@Service
@Validated
public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService {

    private static final int RATIO_SCALE = 4;
    private static final int AMOUNT_SCALE = 2;
    private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
    private static final BigDecimal ONE_RATIO = BigDecimal.ONE.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
    private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);

    @Resource
    private ProjectOutputSplitMapper projectOutputSplitMapper;
    @Resource
    private ProjectPlanningMapper projectPlanningMapper;
    @Resource
    private ProjectMapper projectMapper;
    @Resource
    private ProjectRolePersonMapper projectRolePersonMapper;

    @Override
    public ProjectOutputSplitRespVO getProjectOutputSplit(Long planningId) {
        ProjectPlanningDO planning = validatePlanningExists(planningId);
        ProjectDO project = validateProjectExists(planning.getProjectId());
        ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(planningId);
        if (outputSplit == null) {
            outputSplit = buildDefaultOutputSplit(planning);
        }
        return buildRespVO(outputSplit, planning, project);
    }

    @Override
    public Long saveProjectOutputSplit(ProjectOutputSplitSaveReqVO reqVO) {
        ProjectPlanningDO planning = validatePlanningExists(reqVO.getPlanningId());
        validateOutputSplitRatios(reqVO);
        ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(reqVO.getPlanningId());
        if (outputSplit == null) {
            outputSplit = BeanUtils.toBean(reqVO, ProjectOutputSplitDO.class);
            outputSplit.setProjectId(planning.getProjectId());
            outputSplit.setYear(getPlanningYear(planning));
            normalizeRatios(outputSplit);
            try {
                projectOutputSplitMapper.insert(outputSplit);
            } catch (DuplicateKeyException ex) {
                // 并发下可能已由自动创建流程插入，重新查询后继续更新用户提交的比例。
                outputSplit = projectOutputSplitMapper.selectByPlanningId(reqVO.getPlanningId());
                if (outputSplit == null) {
                    throw ex;
                }
                ProjectOutputSplitDO updateObj = BeanUtils.toBean(reqVO, ProjectOutputSplitDO.class);
                updateObj.setId(outputSplit.getId());
                updateObj.setProjectId(planning.getProjectId());
                updateObj.setYear(getPlanningYear(planning));
                normalizeRatios(updateObj);
                projectOutputSplitMapper.updateById(updateObj);
                outputSplit = updateObj;
            }
        } else {
            ProjectOutputSplitDO updateObj = BeanUtils.toBean(reqVO, ProjectOutputSplitDO.class);
            updateObj.setId(outputSplit.getId());
            updateObj.setProjectId(planning.getProjectId());
            updateObj.setYear(getPlanningYear(planning));
            normalizeRatios(updateObj);
            projectOutputSplitMapper.updateById(updateObj);
            outputSplit = updateObj;
        }
        return outputSplit.getId();
    }

    @Override
    public ProjectOutputSplitDO getOrCreateProjectOutputSplit(Long planningId) {
        ProjectPlanningDO planning = validatePlanningExists(planningId);
        ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(planningId);
        if (outputSplit != null) {
            return outputSplit;
        }
        ProjectOutputSplitDO createObj = buildDefaultOutputSplit(planning);
        try {
            projectOutputSplitMapper.insert(createObj);
            return createObj;
        } catch (DuplicateKeyException ex) {
            // 唯一索引兜底并发创建，同一合约规划已存在时直接复用已有记录。
            ProjectOutputSplitDO existing = projectOutputSplitMapper.selectByPlanningId(planningId);
            if (existing != null) {
                return existing;
            }
            throw ex;
        }
    }

    @Override
    public Map<Long, ProjectOutputSplitDO> getProjectOutputSplitMap(Collection<Long> planningIds) {
        if (planningIds == null || planningIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return projectOutputSplitMapper.selectListByPlanningIds(planningIds).stream()
                .collect(Collectors.toMap(ProjectOutputSplitDO::getPlanningId, item -> item, (a, b) -> b));
    }

    @Override
    public BigDecimal getSpecialtyAmount(ProjectOutputSplitDO outputSplit, BigDecimal assessmentOutputValue, String specialtyCode) {
        if (outputSplit == null) {
            return ZERO_AMOUNT;
        }
        BigDecimal officeAmount = multiplyAmount(amount(assessmentOutputValue), ratio(outputSplit.getOfficeRatio()));
        return multiplyAmount(officeAmount, getSpecialtyRatio(outputSplit, specialtyCode));
    }

    @Override
    public void deleteByPlanningId(Long planningId) {
        ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(planningId);
        if (outputSplit != null) {
            projectOutputSplitMapper.deleteById(outputSplit.getId());
        }
    }

    @Override
    public void deleteByPlanningIds(Collection<Long> planningIds) {
        if (planningIds == null || planningIds.isEmpty()) {
            return;
        }
        projectOutputSplitMapper.delete(new cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX<ProjectOutputSplitDO>()
                .in(ProjectOutputSplitDO::getPlanningId, planningIds));
    }

    private ProjectOutputSplitRespVO buildRespVO(ProjectOutputSplitDO outputSplit, ProjectPlanningDO planning, ProjectDO project) {
        ProjectOutputSplitRespVO respVO = BeanUtils.toBean(outputSplit, ProjectOutputSplitRespVO.class);
        BigDecimal assessmentOutputValue = amount(planning.getAssessmentOutputValue());
        BigDecimal projectLeadAmount = multiplyAmount(assessmentOutputValue, outputSplit.getProjectLeadRatio());
        BigDecimal officeAmount = multiplyAmount(assessmentOutputValue, outputSplit.getOfficeRatio());
        List<ProjectRolePersonDO> rolePersons = projectRolePersonMapper.selectListByProjectId(project.getId());
        String projectManagerName = joinRoleNames(rolePersons, OutputSplitBizConstants.ROLE_PROJECT_MANAGER);
        String engineeringPrincipalName = joinRoleNames(rolePersons, OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL);

        respVO.setProjectName(project.getProjectName());
        respVO.setPlanningContent(planning.getPlanningContent());
        respVO.setYear(getPlanningYear(planning));
        respVO.setAssessmentOutputValue(assessmentOutputValue);
        respVO.setProjectManagerName(projectManagerName);
        respVO.setEngineeringLeaderName(engineeringPrincipalName);
        respVO.setProjectLeadName(joinProjectLeadName(projectManagerName, engineeringPrincipalName));
        respVO.setProjectLeadAmount(projectLeadAmount);
        respVO.setOfficeAmount(officeAmount);
        respVO.setArchAmount(multiplyAmount(officeAmount, outputSplit.getArchRatio()));
        respVO.setDecorAmount(multiplyAmount(officeAmount, outputSplit.getDecorRatio()));
        respVO.setStructAmount(multiplyAmount(officeAmount, outputSplit.getStructRatio()));
        respVO.setWaterAmount(multiplyAmount(officeAmount, outputSplit.getWaterRatio()));
        respVO.setElecAmount(multiplyAmount(officeAmount, outputSplit.getElecRatio()));
        respVO.setHvacAmount(multiplyAmount(officeAmount, outputSplit.getHvacRatio()));
        respVO.setDigitalAmount(multiplyAmount(officeAmount, outputSplit.getDigitalRatio()));
        return respVO;
    }

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

    private ProjectDO validateProjectExists(Long projectId) {
        ProjectDO project = projectMapper.selectById(projectId);
        if (project == null) {
            throw exception(PROJECT_NOT_EXISTS);
        }
        return project;
    }

    private ProjectOutputSplitDO buildDefaultOutputSplit(ProjectPlanningDO planning) {
        ProjectOutputSplitDO outputSplit = new ProjectOutputSplitDO();
        outputSplit.setProjectId(planning.getProjectId());
        outputSplit.setPlanningId(planning.getId());
        outputSplit.setYear(getPlanningYear(planning));
        outputSplit.setProjectLeadRatio(ZERO_RATIO);
        outputSplit.setOfficeRatio(ONE_RATIO);
        outputSplit.setArchRatio(ONE_RATIO);
        outputSplit.setDecorRatio(ZERO_RATIO);
        outputSplit.setStructRatio(ZERO_RATIO);
        outputSplit.setWaterRatio(ZERO_RATIO);
        outputSplit.setElecRatio(ZERO_RATIO);
        outputSplit.setHvacRatio(ZERO_RATIO);
        outputSplit.setDigitalRatio(ZERO_RATIO);
        return outputSplit;
    }

    private Integer getPlanningYear(ProjectPlanningDO planning) {
        return planning.getPlanningStartYear() == null ? 0 : planning.getPlanningStartYear();
    }

    private void validateOutputSplitRatios(ProjectOutputSplitSaveReqVO reqVO) {
        validateRatioRange(reqVO.getProjectLeadRatio());
        validateRatioRange(reqVO.getOfficeRatio());
        validateRatioRange(reqVO.getArchRatio());
        validateRatioRange(reqVO.getDecorRatio());
        validateRatioRange(reqVO.getStructRatio());
        validateRatioRange(reqVO.getWaterRatio());
        validateRatioRange(reqVO.getElecRatio());
        validateRatioRange(reqVO.getHvacRatio());
        validateRatioRange(reqVO.getDigitalRatio());
        BigDecimal projectTotal = ratio(reqVO.getProjectLeadRatio())
                .add(ratio(reqVO.getOfficeRatio()))
                .setScale(RATIO_SCALE, RoundingMode.HALF_UP);
        BigDecimal specialtyTotal = ratio(reqVO.getArchRatio())
                .add(ratio(reqVO.getDecorRatio()))
                .add(ratio(reqVO.getStructRatio()))
                .add(ratio(reqVO.getWaterRatio()))
                .add(ratio(reqVO.getElecRatio()))
                .add(ratio(reqVO.getHvacRatio()))
                .add(ratio(reqVO.getDigitalRatio()))
                .setScale(RATIO_SCALE, RoundingMode.HALF_UP);
        if (projectTotal.compareTo(ONE_RATIO) != 0 || specialtyTotal.compareTo(ONE_RATIO) != 0) {
            throw exception(PROJECT_OUTPUT_SPLIT_RATIO_INVALID);
        }
    }

    private void validateRatioRange(BigDecimal value) {
        BigDecimal normalized = ratio(value);
        if (normalized.compareTo(ZERO_RATIO) < 0 || normalized.compareTo(ONE_RATIO) > 0) {
            throw exception(PROJECT_OUTPUT_SPLIT_RATIO_INVALID);
        }
    }

    private void normalizeRatios(ProjectOutputSplitDO outputSplit) {
        normalize(outputSplit::setProjectLeadRatio, outputSplit.getProjectLeadRatio());
        normalize(outputSplit::setOfficeRatio, outputSplit.getOfficeRatio());
        normalize(outputSplit::setArchRatio, outputSplit.getArchRatio());
        normalize(outputSplit::setDecorRatio, outputSplit.getDecorRatio());
        normalize(outputSplit::setStructRatio, outputSplit.getStructRatio());
        normalize(outputSplit::setWaterRatio, outputSplit.getWaterRatio());
        normalize(outputSplit::setElecRatio, outputSplit.getElecRatio());
        normalize(outputSplit::setHvacRatio, outputSplit.getHvacRatio());
        normalize(outputSplit::setDigitalRatio, outputSplit.getDigitalRatio());
    }

    private void normalize(Consumer<BigDecimal> consumer, BigDecimal value) {
        consumer.accept(ratio(value));
    }

    private BigDecimal getSpecialtyRatio(ProjectOutputSplitDO outputSplit, String specialtyCode) {
        switch (specialtyCode) {
            case OutputSplitBizConstants.SPECIALTY_ARCH:
                return ratio(outputSplit.getArchRatio());
            case OutputSplitBizConstants.SPECIALTY_DECOR:
                return ratio(outputSplit.getDecorRatio());
            case OutputSplitBizConstants.SPECIALTY_STRUCT:
                return ratio(outputSplit.getStructRatio());
            case OutputSplitBizConstants.SPECIALTY_WATER:
                return ratio(outputSplit.getWaterRatio());
            case OutputSplitBizConstants.SPECIALTY_ELEC:
                return ratio(outputSplit.getElecRatio());
            case OutputSplitBizConstants.SPECIALTY_HVAC:
                return ratio(outputSplit.getHvacRatio());
            case OutputSplitBizConstants.SPECIALTY_DIGITAL:
                return ratio(outputSplit.getDigitalRatio());
            default:
                return ZERO_RATIO;
        }
    }

    private BigDecimal multiplyAmount(BigDecimal left, BigDecimal right) {
        return amount(left).multiply(ratio(right)).setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
    }

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

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

    private String joinRoleNames(List<ProjectRolePersonDO> rolePersons, String roleCode) {
        return rolePersons.stream()
                .filter(item -> Objects.equals(item.getRoleCode(), roleCode))
                .map(ProjectRolePersonDO::getEmployeeName)
                .filter(Objects::nonNull)
                .collect(Collectors.joining("、"));
    }

    private String joinProjectLeadName(String projectManagerName, String engineeringPrincipalName) {
        if ((projectManagerName == null || projectManagerName.isEmpty())
                && (engineeringPrincipalName == null || engineeringPrincipalName.isEmpty())) {
            return "";
        }
        if (projectManagerName == null || projectManagerName.isEmpty()) {
            return engineeringPrincipalName;
        }
        if (engineeringPrincipalName == null || engineeringPrincipalName.isEmpty()) {
            return projectManagerName;
        }
        return projectManagerName + " / " + engineeringPrincipalName;
    }

}
