package digital.laboratory.platform.inspection.service.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import digital.laboratory.platform.common.core.util.R;
import digital.laboratory.platform.inspection.enums.StdSolutionNum;
import digital.laboratory.platform.inspection.constant.TestRecordSampleDataConstant;
import digital.laboratory.platform.inspection.dto.TestRecordStandardSolutionDto;
import digital.laboratory.platform.inspetion.api.entity.TestRecord;
import digital.laboratory.platform.inspection.entity.TestRecordStandardSolution;
import digital.laboratory.platform.inspection.enums.TestRecordArgumentType;
import digital.laboratory.platform.inspection.mapper.TestRecordStandardSolutionMapper;
import digital.laboratory.platform.inspection.service.TestRecordReagentService;
import digital.laboratory.platform.inspection.service.TestRecordService;
import digital.laboratory.platform.inspection.service.TestRecordStandardSolutionService;
import digital.laboratory.platform.sys.feign.RemoteBalanceRMaterialService;
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.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
@SuppressWarnings("all")
public class TestRecordStandardSolutionServiceImpl extends ServiceImpl<TestRecordStandardSolutionMapper, TestRecordStandardSolution> implements TestRecordStandardSolutionService {
    @Resource
    private RemoteBalanceRMaterialService remoteBalanceRMaterialService;

    @Resource
    private TestRecordService testRecordService;

    @Resource
    private TestRecordReagentService testRecordReagentService;

    /**
     * 创建标准溶液。
     * <p>
     * 此方法接收一个包含标准溶液信息的数据传输对象（DTO），并将其转换为实体对象进行保存。
     * 同时，根据输入数据计算浓度，并将该标准溶液与相应的实验记录绑定。
     *
     * @param testRecordStandardSolutionDto 包含标准溶液信息的数据传输对象
     * @return 成功保存的标准溶液实体对象，如果保存失败则返回null
     */
    @Override
    @Transactional(rollbackFor = Exception.class)  // 确保事务管理，发生异常时回滚
    public TestRecordStandardSolution addTestRecordStandardSolution(TestRecordStandardSolutionDto testRecordStandardSolutionDto) {

        Integer opCode = 1;  // 操作码，用于标识操作类型

        TestRecordStandardSolution testRecordStandardSolution = new TestRecordStandardSolution();

        // 将DTO中的属性复制到实体对象中
        BeanUtils.copyProperties(testRecordStandardSolutionDto, testRecordStandardSolution);

        String standardName = "";

        // 设置标准物质数量，默认为1，表示单个标准物质溶液
        testRecordStandardSolution.setStandardSubstanceCount(1);

        // 生成标准溶液编号
        testRecordStandardSolution.setNumber(this.createNumber(testRecordStandardSolution));

        // 生成唯一ID
        testRecordStandardSolution.setId(IdWorker.get32UUID().toUpperCase());

        // 设置有效期为制备日期后7天
        testRecordStandardSolution.setExpirationDate(testRecordStandardSolutionDto.getMadeDate().plusDays(7));

        // 设置溶液类型为标准工作溶液(STD)
        testRecordStandardSolution.setSolutionType(TestRecordSampleDataConstant.SAMPLE_TYPE_STD);

        // 如果提供了称取质量/移取体积，则计算浓度；否则假设用户已填写好浓度
        if (StringUtils.isNotBlank(testRecordStandardSolution.getWeighingMoving())) {
            testRecordStandardSolution = this.calculatedConcentration(testRecordStandardSolution);
        } else {
            testRecordStandardSolution.setWeighingMoving(testRecordStandardSolution.getResultConcentration());
            testRecordStandardSolution.setDilutionFactor("1");
            testRecordStandardSolution.setOriginalConcentration(testRecordStandardSolution.getResultConcentration());
            testRecordStandardSolution.setConstantVolume("1");
        }

        // 命名溶液名称
        testRecordStandardSolution.setStandardName(testRecordStandardSolutionDto.getStandardName()
                + testRecordStandardSolution.getDilutionName() + "溶液");

        // 将该标准溶液与对应的实验记录进行绑定
        boolean ret = testRecordService.updateTestRecordArgument(
                testRecordStandardSolution.getTestId(),
                testRecordStandardSolution.getId(),
                TestRecordArgumentType.TEST_RECORD_ARGUMENT_STANDARD_SOLUTION.getType(),
                opCode
        );

        // 保存标准溶液实体对象，并检查是否成功保存以及是否成功更新实验记录参数
        return this.save(testRecordStandardSolution) && ret ? testRecordStandardSolution : null;
    }

    /**
     * 生成空白溶液。
     * <p>
     * 根据提供的实验ID和类型（空白溶剂或空白样品），创建并保存一个空白溶液记录。
     * 编号生成规则基于当前日期和已有溶液的数量进行编号。
     *
     * @param testId 实验ID
     * @param type   类型标识，1表示空白溶剂，-1表示空白样品
     * @return 成功保存的标准溶液实体对象，如果保存失败则返回null
     */
    @Override
    public TestRecordStandardSolution addBlankSolution(String testId, Integer type) {
        // 创建新的标准溶液实体对象
        TestRecordStandardSolution solution = new TestRecordStandardSolution();

        // 设置标准物质数量为0，表示空白溶液
        solution.setStandardSubstanceCount(0);

        // 设置实验ID
        solution.setTestId(testId);

        // 设置溶液类型和名称
        String solutionType;
        String standardName;
        String prefix;
        if (type == 1) {  // 空白溶剂
            solutionType = "BLK";  // 溶液类型：空白溶剂
            standardName = "空白溶剂";  // 名称：空白溶剂
            prefix = StdSolutionNum.BLANK_SOLUTION.getPrefix();  // 前缀用于生成编号
        } else {  // 空白样品
            solutionType = "BLS";  // 溶液类型：空白样品
            standardName = "空白样品";  // 名称：空白样品
            prefix = StdSolutionNum.BLANK_SAMPLE_SOLUTION.getPrefix();  // 前缀用于生成编号
        }
        solution.setStandardName(standardName);  // 设置标准溶液名称
        solution.setSolutionType(solutionType);  // 设置溶液类型

        // 查询已有的溶液列表以生成编号
        List<TestRecordStandardSolution> solutionList = this.list(
                Wrappers.<TestRecordStandardSolution>lambdaQuery()
                        .eq(TestRecordStandardSolution::getSolutionType, solutionType)  // 过滤条件：溶液类型
                        .eq(TestRecordStandardSolution::getTestId, testId)  // 过滤条件：实验ID
        );

        // 生成编号
        String suffix = solutionList == null || solutionList.isEmpty() ? "01" : String.format("%02d", solutionList.size() + 1);
        String number = prefix + "-" + LocalDateTimeUtil.format(LocalDateTime.now(), "yyyyMMdd") + "-" + suffix;
        solution.setNumber(number);  // 设置生成的编号

        // 设置其他属性
        solution.setResultConcentration("/");  // 结果浓度默认值
        solution.setMadeDate(LocalDateTime.now());  // 制备日期设置为当前时间
        solution.setOriginalConcentration("/");  // 原始浓度默认值
        solution.setWeighingMoving("0");  // 称取质量/移取体积默认值
        solution.setConstantVolume("0");  // 定容体积默认值
        solution.setDilutionFactor("0");  // 稀释倍数默认值
        solution.setDilutionName("/");  // 稀释物质名称默认值
        solution.setExpirationDate(LocalDateTime.now());  // 有效期设置为当前时间
        solution.setId(IdWorker.get32UUID().toUpperCase());  // 设置唯一ID
        solution.setResultConcentrationUnit("/");  //设置单位

        // 更新实验记录参数，将该标准溶液与对应的实验记录进行绑定
        boolean ret = testRecordService.updateTestRecordArgument(
                solution.getTestId(),
                solution.getId(),
                TestRecordArgumentType.TEST_RECORD_ARGUMENT_STANDARD_SOLUTION.getType(),
                1
        );

        // 保存标准溶液实体对象，并检查是否成功保存以及是否成功更新实验记录参数
        return this.save(solution) && ret ? solution : null;
    }

    /**
     * 根据标准溶液种数创建质控溶液。
     *
     * @param testId 实验ID
     * @return 成功保存的质控溶液列表，如果保存失败则返回null
     */
    @Override
    public List<TestRecordStandardSolution> createQualityControlSol(String testId) {
        // 获取实验记录
        TestRecord record = testRecordService.getById(testId);

        // 查询所有非空白和非质控的标准溶液
        List<TestRecordStandardSolution> standardSolutions = this.list(
                Wrappers.<TestRecordStandardSolution>lambdaQuery()
                        .eq(TestRecordStandardSolution::getSolutionType, "STD")
                        .eq(TestRecordStandardSolution::getTestId, testId)
        );

        // 检查是否有标准溶液
        if (standardSolutions.isEmpty()) {
            throw new RuntimeException("请先添加至少一瓶标准溶液后再进行添加质控溶液!");
        }

        // 计算所有标准溶液中的标准物质数量总和

        int totalSubstanceCount = standardSolutions.size();

        // 清除现有的质控溶液

        List<TestRecordStandardSolution> qcList = this.list(
                Wrappers.<TestRecordStandardSolution>lambdaQuery()
                        .eq(TestRecordStandardSolution::getStandardSubstanceCount, 0)
                        .eq(TestRecordStandardSolution::getTestId, testId)
                        .likeRight(TestRecordStandardSolution::getNumber, TestRecordSampleDataConstant.SAMPLE_TYPE_QC)
        );

        // 删除现有质控溶液
        if (!qcList.isEmpty()) {
            this.remove(Wrappers.<TestRecordStandardSolution>lambdaQuery()
                    .eq(TestRecordStandardSolution::getStandardSubstanceCount, 0)
                    .eq(TestRecordStandardSolution::getTestId, testId)
                    .likeRight(TestRecordStandardSolution::getNumber, TestRecordSampleDataConstant.SAMPLE_TYPE_QC));

            // 更新实验记录中的标准溶液列表，移除旧的质控溶液ID
            List<String> standardSolutionIds = record.getStandardSolution();
            for (TestRecordStandardSolution qcSolution : qcList) {
                standardSolutionIds.remove(qcSolution.getId());
            }
        }

        // 创建新的质控溶液
        List<TestRecordStandardSolution> qcSolutions = new ArrayList<>();
        for (int i = 0; i < totalSubstanceCount; i++) {
            TestRecordStandardSolution qcSolution = new TestRecordStandardSolution();
            qcSolution.setStandardSubstanceCount(0);  // 设置标准物质数量为0
            qcSolution.setTestId(testId);  // 设置实验ID
            qcSolution.setStandardName("标准添加样品");  // 设置名称为“标准添加样品”
            String suffix = String.format("%02d", i + 1);  // 生成编号后缀
            String number = StdSolutionNum.QC_SOLUTION.getPrefix() + "-" + LocalDateTimeUtil.format(LocalDateTime.now(), "yyyyMMdd") + "-" + suffix;  // 生成编号
            qcSolution.setNumber(number);  // 设置编号
            qcSolution.setResultConcentration("/");  // 设置结果浓度默认值
            qcSolution.setMadeDate(LocalDateTime.now());  // 设置制备日期为当前时间
            qcSolution.setOriginalConcentration("/");  // 设置原始浓度默认值
            qcSolution.setWeighingMoving("0");  // 设置称取质量/移取体积默认值
            qcSolution.setConstantVolume("0");  // 设置定容体积默认值
            qcSolution.setDilutionFactor("0");  // 设置稀释倍数默认值
            qcSolution.setDilutionName("/");  // 设置稀释物质名称默认值
            qcSolution.setExpirationDate(LocalDateTime.now());  // 设置有效期为当前时间
            qcSolution.setId(IdWorker.get32UUID().toUpperCase());  // 设置唯一ID
            qcSolution.setSolutionType(TestRecordSampleDataConstant.SAMPLE_TYPE_QC);  // 设置溶液类型为质控溶液
            qcSolution.setResultConcentrationUnit("/");  // 设置单位
            qcSolutions.add(qcSolution);  // 添加到质控溶液列表中
        }


        // 更新实验记录中的标准溶液列表，添加新的质控溶液ID
        List<String> standardSolutionIds = record.getStandardSolution();
        for (TestRecordStandardSolution qcSolution : qcSolutions) {
            standardSolutionIds.add(qcSolution.getId());  // 添加新的质控溶液ID
        }
        record.setStandardSolution(standardSolutionIds);  // 更新实验记录中的标准溶液列表
        testRecordService.updateById(record);  // 更新实验记录
        // 批量保存新创建的质控溶液
        return this.saveBatch(qcSolutions) ? qcSolutions : null;
    }


    /**
     * 创建标准溶液编号
     *
     * @param standardSubstanceNumList
     * @param orderNum                 溶液编号后缀，用来匹配称量系统那边的称量编号
     * @return
     */
    public String createNumber(TestRecordStandardSolution solution) {
        String number = "";
        String englishName = solution.getEnglishName();
        String date = LocalDateTimeUtil.format(LocalDateTime.now(), "yyyyMMdd");
        return englishName + "-" + date;
    }

    /**
     * 删除标准溶液
     *
     * @param id
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delTestRecordStandardSolution(String id) {
        Integer opCode = -1;
        TestRecordStandardSolution testRecordStandardSolution = this.getById(id);
        if (testRecordStandardSolution == null) {
            throw new RuntimeException(String.format("没有找到ID为%s的标准溶液", id));
        }
        boolean ret = testRecordService.updateTestRecordArgument(testRecordStandardSolution.getTestId(), testRecordStandardSolution.getId(), TestRecordArgumentType.TEST_RECORD_ARGUMENT_STANDARD_SOLUTION.getType(), opCode);
        return this.removeById(id) && ret;
    }

    /**
     * 修改标准溶液
     *
     * @param testRecordStandardSolution
     * @return
     */
    @Override
    public TestRecordStandardSolution updateTestRecordStandardSolution(TestRecordStandardSolutionDto testRecordStandardSolutionDto) {
        TestRecordStandardSolution oldTestRecordStandardSolution = this.getOne(Wrappers.<TestRecordStandardSolution>lambdaQuery().eq(TestRecordStandardSolution::getId, testRecordStandardSolutionDto.getId()));
        if (oldTestRecordStandardSolution == null) {
            throw new RuntimeException(String.format("没有找到ID为%s的标准溶液", testRecordStandardSolutionDto.getId()));
        }
        TestRecordStandardSolution solution = new TestRecordStandardSolution();
        BeanUtils.copyProperties(testRecordStandardSolutionDto, solution);
        solution.setNumber(this.createNumber(solution));
        solution.setExpirationDate(solution.getMadeDate().plusDays(7));

        // 如果提供了称取质量/移取体积，则计算浓度；否则假设用户已填写好浓度
//        if (!testRecordStandardSolutionDto.getWeighingMoving().isEmpty()) {
//            solution = this.calculatedConcentration(solution);
//        } else {
        solution.setWeighingMoving(solution.getResultConcentration());
        solution.setDilutionFactor("1");
        solution.setOriginalConcentration(solution.getResultConcentration());
        solution.setConstantVolume("1");
//        }
        return this.updateById(solution) ? solution : null;
    }

    /**
     * 计算溶液浓度
     *
     * @param testRecordStandardSolution
     * @return
     */
    public TestRecordStandardSolution calculatedConcentration(TestRecordStandardSolution testRecordStandardSolution) {
        //计算原始浓度
        if (StrUtil.isNotBlank(testRecordStandardSolution.getWeighingMoving())) {
            BigDecimal weighingValue = new BigDecimal(testRecordStandardSolution.getWeighingMoving());
            BigDecimal constantVolume = new BigDecimal(testRecordStandardSolution.getConstantVolume());
            BigDecimal originalConcentration = weighingValue.divide(constantVolume, 2, RoundingMode.HALF_UP);
            testRecordStandardSolution.setOriginalConcentration(originalConcentration.toString());
        }
        //计算结果浓度
        if (StrUtil.isNotBlank(testRecordStandardSolution.getOriginalConcentration()) && StrUtil.isNotBlank(testRecordStandardSolution.getDilutionFactor())) {
            BigDecimal originalConcentration = new BigDecimal(testRecordStandardSolution.getOriginalConcentration());
            BigDecimal dilutionFactor = new BigDecimal(testRecordStandardSolution.getDilutionFactor());
            BigDecimal resultConcentration = originalConcentration.divide(dilutionFactor, 2, RoundingMode.HALF_UP);
            testRecordStandardSolution.setResultConcentration(resultConcentration.toString());
        }
        return testRecordStandardSolution;
    }

    /**
     * 通过实验ID分页查询
     *
     * @param page
     * @param testId
     * @return
     */
    @Override
    public IPage<TestRecordStandardSolution> getTestRecordStandardSolutionPage(Page<TestRecordStandardSolution> page, String testId, String keywords) {
        return baseMapper.getTestRecordStandardSolutionMapPage(page, testId);
    }

    /**
     * 通过实验ID列表查询
     *
     * @param testId
     * @param keywords
     * @return
     */
    @Override
    public List<TestRecordStandardSolution> getTestRecordStandardSolutionList(String testId, String keywords) {
        return baseMapper.getTestRecordStandardSolutionMapList(testId);
    }

    /**
     * 匹配称量数据
     *
     * @param id
     * @return
     */
    @Override
    public List<TestRecordStandardSolution> matchingWeighing(String id) {
        List<TestRecordStandardSolution> testRecordStandardSolutionList = this.list(Wrappers.<TestRecordStandardSolution>lambdaQuery().eq(TestRecordStandardSolution::getTestId, id));
        if (testRecordStandardSolutionList == null || testRecordStandardSolutionList.size() == 0) {
            throw new RuntimeException(String.format("没有找到ID为%s的实验具有相关的标准溶液信息!", id));
        }

        //如果匹配到了称量质量，那么就自动去计算浓度
        for (TestRecordStandardSolution testRecordStandardSolution : testRecordStandardSolutionList) {
            String standardSolutionNumber = testRecordStandardSolution.getNumber();
            String number = standardSolutionNumber.substring(4);
            R<BigDecimal> data = remoteBalanceRMaterialService.getWeightForNumber(number);
            BigDecimal weighing = data.getData();
            if (weighing.compareTo(BigDecimal.ZERO) != 0) {
                testRecordStandardSolution.setWeighingMoving(weighing.toString());
                BigDecimal constantVolume = new BigDecimal(testRecordStandardSolution.getConstantVolume());
                BigDecimal originalConcentration = weighing.divide(constantVolume, 2, RoundingMode.HALF_UP);
                testRecordStandardSolution.setOriginalConcentration(originalConcentration.toString());

                if (StrUtil.isNotBlank(testRecordStandardSolution.getDilutionFactor())) {
                    BigDecimal dilutionFactor = new BigDecimal(testRecordStandardSolution.getDilutionFactor());
                    BigDecimal resultConcentration = originalConcentration.divide(dilutionFactor, 2, RoundingMode.HALF_UP);
                    testRecordStandardSolution.setResultConcentration(resultConcentration.toString());
                } else {
                    testRecordStandardSolution.setResultConcentration(originalConcentration.toString());
                }
            }
        }
        return this.updateBatchById(testRecordStandardSolutionList) ? testRecordStandardSolutionList : null;
    }

    @Override
    public boolean detection(String testId) {
        List<TestRecordStandardSolution> stdList = this.list(Wrappers.<TestRecordStandardSolution>lambdaQuery().eq(TestRecordStandardSolution::getTestId, testId).eq(TestRecordStandardSolution::getSolutionType, TestRecordSampleDataConstant.SAMPLE_TYPE_STD));
        if (stdList == null || stdList.size() < 1) {
            return false;
        } else return true;
    }
}
