package com.aucma.base.service.impl;

import com.aucma.base.domain.BaseBizCodeSequence;
import com.aucma.base.mapper.BaseBizCodeSequenceMapper;
import com.aucma.base.service.IBusinessCodeGeneratorService;
import com.aucma.common.exception.ServiceException;
import com.aucma.common.utils.DateUtils;
import com.aucma.common.utils.StringUtils;
import com.aucma.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
 * 统一业务编码生成服务实现
 *
 * @author Codex
 */
@Service
public class BusinessCodeGeneratorServiceImpl implements IBusinessCodeGeneratorService {
    private static final String SAP_PLAN_BIZ_TYPE = "SAP_PLAN";
    private static final String SAP_PLAN_DATE_PATTERN_KEY = "mes.biz.code.sapPlan.datePattern";
    private static final String SAP_PLAN_SEQ_LENGTH_KEY = "mes.biz.code.sapPlan.seqLength";
    private static final String DEFAULT_DATE_PATTERN = "yyyyMM";
    private static final int DEFAULT_SEQ_LENGTH = 4;

    @Autowired
    private BaseBizCodeSequenceMapper baseBizCodeSequenceMapper;

    @Autowired
    private ISysConfigService sysConfigService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String generateSapPlanCode(Date businessDate) {
        Date currentBusinessDate = businessDate != null ? businessDate : DateUtils.getNowDate();
        String datePattern = getConfigValue(SAP_PLAN_DATE_PATTERN_KEY, DEFAULT_DATE_PATTERN);
        int seqLength = parseSeqLength(getConfigValue(SAP_PLAN_SEQ_LENGTH_KEY, String.valueOf(DEFAULT_SEQ_LENGTH)));
        String currentPeriod = DateUtils.parseDateToStr(datePattern, currentBusinessDate);

        BaseBizCodeSequence sequence = getLockedSequence(SAP_PLAN_BIZ_TYPE, currentPeriod);
        Long nextSeq = calculateNextSeq(sequence, currentPeriod);
        sequence.setCurrentPeriod(currentPeriod);
        sequence.setCurrentSeq(nextSeq);
        sequence.setUpdatedTime(DateUtils.getNowDate());
        baseBizCodeSequenceMapper.updateBaseBizCodeSequence(sequence);

        return currentPeriod + StringUtils.leftPad(String.valueOf(nextSeq), seqLength, '0');
    }

    private BaseBizCodeSequence getLockedSequence(String bizType, String currentPeriod) {
        BaseBizCodeSequence sequence = baseBizCodeSequenceMapper.selectByBizTypeForUpdate(bizType);
        if (sequence != null) {
            return sequence;
        }

        BaseBizCodeSequence initSequence = new BaseBizCodeSequence();
        initSequence.setBizType(bizType);
        initSequence.setCurrentPeriod(currentPeriod);
        initSequence.setCurrentSeq(0L);
        initSequence.setRemark("统一业务编码序列");
        initSequence.setCreatedTime(DateUtils.getNowDate());
        initSequence.setUpdatedTime(DateUtils.getNowDate());
        try {
            baseBizCodeSequenceMapper.insertBaseBizCodeSequence(initSequence);
        } catch (DuplicateKeyException ex) {
            // 为什么忽略重复插入异常：并发场景下允许只有一个事务初始化成功，其他事务随后重新加锁读取即可。
        }

        sequence = baseBizCodeSequenceMapper.selectByBizTypeForUpdate(bizType);
        if (sequence == null) {
            throw new ServiceException("业务编码序列初始化失败，请检查基础数据配置");
        }
        return sequence;
    }

    private Long calculateNextSeq(BaseBizCodeSequence sequence, String currentPeriod) {
        if (!StringUtils.equals(sequence.getCurrentPeriod(), currentPeriod)) {
            // 为什么跨周期要从 1 重新计数：编号规则要求前缀按年月分段，序号必须在新周期内重新累计。
            return 1L;
        }
        Long currentSeq = sequence.getCurrentSeq() == null ? 0L : sequence.getCurrentSeq();
        return currentSeq + 1L;
    }

    private String getConfigValue(String configKey, String defaultValue) {
        String configValue = sysConfigService.selectConfigByKey(configKey);
        return StringUtils.isBlank(configValue) ? defaultValue : configValue.trim();
    }

    private int parseSeqLength(String seqLengthValue) {
        try {
            int seqLength = Integer.parseInt(seqLengthValue);
            if (seqLength <= 0) {
                return DEFAULT_SEQ_LENGTH;
            }
            return seqLength;
        } catch (NumberFormatException ex) {
            return DEFAULT_SEQ_LENGTH;
        }
    }
}
