package jnpf.franchisee.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import jnpf.certificate.helper.OrganizationHelper;
import jnpf.certificate.mapper.CertificateInstanceMapper;
import jnpf.certificate.util.CertificateStatusUtils;
import jnpf.franchisee.mapper.FranchiseeCustomFieldMapper;
import jnpf.franchisee.mapper.FranchiseeExperienceMapper;
import jnpf.franchisee.mapper.FranchiseeJoinRegionMapper;
import jnpf.franchisee.mapper.FranchiseeMapper;
import jnpf.franchisee.service.FranchiseeService;
import jnpf.model.certificate.po.CertificateInstanceEntity;
import jnpf.model.franchisee.po.FranchiseeCustomFieldEntity;
import jnpf.model.franchisee.po.FranchiseeEntity;
import jnpf.model.franchisee.po.FranchiseeExperienceEntity;
import jnpf.model.franchisee.po.FranchiseeJoinRegionEntity;
import jnpf.model.franchisee.req.FranchiseeAddReq;
import jnpf.model.franchisee.req.FranchiseeCustomFieldReq;
import jnpf.model.franchisee.req.FranchiseeExperienceReq;
import jnpf.model.franchisee.req.FranchiseeJoinRegionReq;
import jnpf.model.franchisee.req.FranchiseeQueryReq;
import jnpf.model.franchisee.req.FranchiseeUpdateReq;
import jnpf.model.franchisee.vo.*;
import jnpf.model.warningnotice.enums.CertificateTypeEnum;
import jnpf.model.warningnotice.po.FtbParamEntity;
import jnpf.parameter.hepler.MysqlSequenceHelper;
import jnpf.permission.StoreApi;
import jnpf.permission.V2UserApi;
import jnpf.permission.vo.store.StoreInfoDetailVO;
import jnpf.permission.vo.store.StoreRegionVO;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.storecertificatephoto.mapper.BaseParamMapper;
import jnpf.util.*;
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.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 加盟商服务实现
 */
@Service
public class FranchiseeServiceImpl extends ServiceImpl<FranchiseeMapper, FranchiseeEntity> implements FranchiseeService {

    private static final String EMPTY_JSON_ARRAY = "[]";

    private static final int MAX_EXPERIENCE_COUNT = 5;

    private static final String FRANCHISEE_CODE_PREFIX = "JMS";

    private static final String FRANCHISEE_CODE_SEQ_KEY_PREFIX = "franchiseeCodeSeq_";

    private static final DateTimeFormatter FRANCHISEE_CODE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
    private static final Long FRANCHISEE_CODE_MAX_SEQUENCE = 10000L;

    @Autowired
    private FranchiseeCustomFieldMapper franchiseeCustomFieldMapper;
    @Autowired
    private FranchiseeExperienceMapper franchiseeExperienceMapper;
    @Autowired
    private FranchiseeJoinRegionMapper franchiseeJoinRegionMapper;
    @Autowired
    private BaseParamMapper baseParamMapper;
    @Autowired
    private MysqlSequenceHelper mysqlSequenceHelper;
    @Autowired
    private StoreApi storeApi;
    @Autowired
    private CertificateInstanceMapper certificateInstanceMapper;
    @Autowired
    private OrganizationHelper organizationHelper;
    @Autowired
    private V2UserApi v2UserApi;

    /**
     * 新增加盟商
     *
     * @param req 新增参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(FranchiseeAddReq req) {
        List<FranchiseeCustomFieldReq> customFieldReqList = validateAddReq(req);
        List<FranchiseeJoinRegionReq> joinRegionReqs = normalizeJoinRegionList(req.getJoinRegion());
        FranchiseeEntity entity = req.convert();

        insertFranchiseeEntity(entity);
        saveCustomField(entity.getId(), customFieldReqList);
        saveExperience(entity.getId(), req.getExperienceList());
        saveJoinRegion(entity.getId(), joinRegionReqs);
    }

    /**
     * 编辑加盟商
     *
     * @param req 编辑参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(FranchiseeUpdateReq req) {
        List<FranchiseeCustomFieldReq> customFieldReqList = validateAddReq(req);
        List<FranchiseeJoinRegionReq> joinRegionReqs = normalizeJoinRegionList(req.getJoinRegion());
        String id = StrUtil.trim(req.getId());
        if (StrUtil.isBlank(id)) {
            throw new RuntimeException("主键ID不能为空");
        }

        FranchiseeEntity dbEntity = getEntityById(id);
        FranchiseeEntity entity = req.convert();
        entity.setLastModifyTime(new Date());
        entity.setLastModifyUserId(UserProvider.getLoginUserId());
        entity.setId(dbEntity.getId());
        // 门店数量由门店变更事件维护，编辑时保持原值避免被覆盖
//        entity.setStoreNum(dbEntity.getStoreNum());
        entity.setEnabledMark(dbEntity.getEnabledMark());
        updateFranchiseeEntity(entity);

        deleteCustomFieldByFranchiseeId(id);
        saveCustomField(id, customFieldReqList);

        deleteExperienceByFranchiseeId(id);
        saveExperience(id, req.getExperienceList());

        deleteJoinRegionByFranchiseeId(id);
        saveJoinRegion(id, joinRegionReqs);
    }

    /**
     * 删除加盟商
     *
     * @param id 主键ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String id) {
        String franchiseeId = StrUtil.trim(id);
        if (StrUtil.isBlank(franchiseeId)) {
            throw new RuntimeException("主键ID不能为空");
        }

        FranchiseeEntity franchiseeEntity = getEntityById(franchiseeId);
        if (queryRealStoreNum(franchiseeId) > 0) {
            throw new RuntimeException("加盟商已关联门店，不能删除");
        }
        baseMapper.update(null, Wrappers.<FranchiseeEntity>lambdaUpdate()
                .eq(FranchiseeEntity::getId, franchiseeId)
                .set(FranchiseeEntity::getLastModifyTime,new Date())
                .set(FranchiseeEntity::getLastModifyUserId, UserProvider.getLoginUserId())
                .set(FranchiseeEntity::getEnabledMark, 1)
                //唯一索引，更新为墓碑值
                .set(FranchiseeEntity::getFranchiseeName,id)
                .set(FranchiseeEntity::getFranchiseeCode,id)
                .set(FranchiseeEntity::getTenantId,franchiseeEntity.getFranchiseeName()+","+franchiseeEntity.getFranchiseeCode()));
    }

    /**
     * 根据ID查询加盟商详情
     *
     * @param id 主键ID
     * @return 加盟商详情
     */
    @Override
    public FranchiseeVO queryInfo(String id) {
        String franchiseeId = StrUtil.trim(id);
        if (StrUtil.isBlank(franchiseeId)) {
            throw new RuntimeException("主键ID不能为空");
        }

        FranchiseeEntity entity = getEntityById(franchiseeId);
        FranchiseeVO vo = FranchiseeVO.convert(entity);
        vo.setCustomFieldList(queryCustomFieldList(franchiseeId));
        vo.setExperienceList(queryExperienceList(franchiseeId));
        vo.setJoinRegion(queryJoinRegionList(franchiseeId));
        return vo;
    }

    private String queryJoinRegionList(String franchiseeId) {
        List<FranchiseeJoinRegionEntity> joinRegionEntities = franchiseeJoinRegionMapper.selectList(new LambdaQueryWrapper<FranchiseeJoinRegionEntity>()
                .eq(FranchiseeJoinRegionEntity::getFranchiseeId,franchiseeId));
        if(CollectionUtil.isEmpty(joinRegionEntities)){
            return "[]";
        }
        return JSONUtil.toJsonStr(joinRegionEntities);
    }

    /**
     * 分页查询加盟商列表
     *
     * @param req 查询参数
     * @return 分页数据
     */
    @Override
    public PageInfo<FranchiseePageVO> queryPage(FranchiseeQueryReq req,boolean includeJoinRegion) {
        FranchiseeQueryReq queryReq = req == null ? new FranchiseeQueryReq() : req;
        validateQueryReq(queryReq);
        List<FranchiseeJoinRegionReq> normalizedJoinRegionReqs = normalizeJoinRegionList(queryReq.getJoinRegion());
        List<FranchiseeJoinRegionReq> provinceCityJoinRegion = extractProvinceCityJoinRegion(normalizedJoinRegionReqs);
        List<String> provinceList = extractProvinceList(normalizedJoinRegionReqs);
        queryReq.setJoinRegion(expandQueryJoinRegionList(normalizedJoinRegionReqs));

        int currentPage = queryReq.getCurrentPage() == null || queryReq.getCurrentPage() < 1 ? 1 : queryReq.getCurrentPage();
        int pageSize = queryReq.getPageSize() == null || queryReq.getPageSize() < 1 ? 10 : queryReq.getPageSize();
        queryReq.setCurrentPage(currentPage);
        queryReq.setPageSize(pageSize);

        PageHelper.startPage(currentPage, pageSize);
        List<FranchiseePageVO> result = baseMapper.queryPageList(queryReq, provinceCityJoinRegion, provinceList);
        if(includeJoinRegion){
            buildJoinRegions(result);
        }
        return new PageInfo<>(result);
    }

    private void buildJoinRegions(Collection<FranchiseePageVO> result) {
        if(CollUtil.isEmpty(result)){
            return;
        }
        Map<String,List<FranchiseeJoinRegionEntity>> joinRegionsMap = buildJoinRegionsMap(result.stream().map(FranchiseePageVO::getId).collect(Collectors.toSet()));
        for (FranchiseePageVO franchiseePageVO:result){
            List<FranchiseeJoinRegionEntity> joinRegionEntities = joinRegionsMap.get(franchiseePageVO.getId());
            if(CollUtil.isEmpty(joinRegionEntities)){
                franchiseePageVO.setJoinRegion("[]");
                continue;
            }
            franchiseePageVO.setJoinRegion(JSONUtil.toJsonStr(joinRegionEntities));
        }
    }

    private Map<String,List<FranchiseeJoinRegionEntity>> buildJoinRegionsMap(Collection<String> franchiseeIds){
        List<FranchiseeJoinRegionEntity> joinRegionEntities = franchiseeJoinRegionMapper.selectList(new LambdaQueryWrapper<FranchiseeJoinRegionEntity>()
                .in(FranchiseeJoinRegionEntity::getFranchiseeId,franchiseeIds));
        Map<String,List<FranchiseeJoinRegionEntity>> joinRegions = new HashMap<>();
        for (FranchiseeJoinRegionEntity franchiseeJoinRegionEntity:joinRegionEntities){
            String franchiseeId = franchiseeJoinRegionEntity.getFranchiseeId();
            List<FranchiseeJoinRegionEntity> franchiseeJoinRegionEntities = joinRegions.computeIfAbsent(franchiseeId,v->new ArrayList<>());
            franchiseeJoinRegionEntities.add(franchiseeJoinRegionEntity);
        }
        return joinRegions;
    }

    /**
     * 根据关键字查询加盟商ID列表（模糊匹配加盟商名称）
     *
     * @param keyword 关键字（可匹配加盟商名称）
     * @return 加盟商ID列表
     */
    @Override
    public List<FranchiseeIdName> queryIdListByPrefix(String keyword) {
        String searchKeyword = StrUtil.trim(keyword);
        LambdaQueryWrapper<FranchiseeEntity> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.select(FranchiseeEntity::getId,
                FranchiseeEntity::getFranchiseeName,
                FranchiseeEntity::getMobilePhone,
                FranchiseeEntity::getFranchiseeCode,
                FranchiseeEntity::getCreatorTime);
        queryWrapper.eq(FranchiseeEntity::getEnabledMark, 0);
        queryWrapper.like(StringUtil.isNotBlank(searchKeyword),FranchiseeEntity::getFranchiseeCode, searchKeyword);
        queryWrapper.orderByDesc(FranchiseeEntity::getCreatorTime);

        List<FranchiseeEntity> entityList = baseMapper.selectList(queryWrapper);
        if (CollUtil.isEmpty(entityList)) {
            return Collections.emptyList();
        }
        return entityList.stream()
                .map(e->new FranchiseeIdName(e.getId(),e.getFranchiseeName(),e.getMobilePhone(),e.getFranchiseeCode()))
                .collect(Collectors.toList());
    }

    /**
     * 根据加盟商ID查询其下门店列表。
     *
     * @param franchiseeId 加盟商ID
     * @return 门店列表
     */
    @Override
    public List<FranchiseeStoreVO> queryStoreList(String franchiseeId) {
        String targetFranchiseeId = StrUtil.trim(franchiseeId);
        if (StrUtil.isBlank(targetFranchiseeId)) {
            throw new RuntimeException("加盟商ID不能为空");
        }

        FranchiseeEntity franchiseeEntity = getEntityById(targetFranchiseeId);
        Integer dbStoreNum = franchiseeEntity.getStoreNum();

        Map<String, List<StoreInfoDetailVO>> storeBaseListInfos = organizationHelper.buildStoreInfoDetailsByFranchiseeId(targetFranchiseeId);
        if(CollectionUtil.isEmpty(storeBaseListInfos)){
            return Collections.emptyList();
        }
        List<StoreInfoDetailVO> storeInfoDetailVOS = storeBaseListInfos.get(targetFranchiseeId);
        if(CollectionUtil.isEmpty(storeInfoDetailVOS)){
            return Collections.emptyList();
        }
        //数据不一致 fixme看是否需要增加兜底逻辑，会增加并发处理逻辑更复杂
        if(Objects.nonNull(dbStoreNum) && dbStoreNum != storeInfoDetailVOS.size()){
            storeInfoDetailVOS = updateFranchiseeStoreNum(franchiseeEntity,storeInfoDetailVOS);
        }
        Set<String> userIds = new HashSet<>();
        Set<String> storeIds = new HashSet<>();
        for (StoreInfoDetailVO storeBaseListInfo : storeInfoDetailVOS){
            String storeHeadUserId = storeBaseListInfo.getStoreHeadUserId();
            if(StringUtil.isNotBlank(storeHeadUserId)){
                userIds.add(storeHeadUserId);
            }
            String storeId = storeBaseListInfo.getId();
            if(StringUtil.isNotBlank(storeId)){
                storeIds.add(storeId);
            }
        }

        Map<String, UserBoundVO> userBoundVOMap = organizationHelper.getUserPrimaryBoundBatch(userIds);

        Map<String,List<CertificateInstanceEntity>> certificateMap = certificateInstanceMapper.selectList(new LambdaQueryWrapper<CertificateInstanceEntity>()
                .in(CertificateInstanceEntity::getSubjectId,storeIds)
                .in(CertificateInstanceEntity::getSubjectType,3)
                .eq(CertificateInstanceEntity::getEnabledMark,0))
                .stream()
                .collect(Collectors.groupingBy(CertificateInstanceEntity::getSubjectId));//门店
        return storeInfoDetailVOS.stream()
                .map(s->convertStoreVO(s,userBoundVOMap,certificateMap))
                .collect(Collectors.toList());
    }

    private List<StoreInfoDetailVO> updateFranchiseeStoreNum(FranchiseeEntity franchiseeEntity, List<StoreInfoDetailVO> storeInfoDetailVOS) {
        String franchiseeId = franchiseeEntity.getId();
        Integer dbStoreNum = franchiseeEntity.getStoreNum();
        Integer currentStoreNum = storeInfoDetailVOS.size();
        if(Objects.equals(dbStoreNum, currentStoreNum)){//已经相等了，退出
            return storeInfoDetailVOS;
        }
        int result = baseMapper.update(null,new LambdaUpdateWrapper<FranchiseeEntity>()
                .set(FranchiseeEntity::getStoreNum,currentStoreNum)
                        .set(FranchiseeEntity::getUpdateStoreNumTimestamp,System.currentTimeMillis())//事件水位线。需要和mq消费事件水位获取来源一致。目前来看，他们是用得数据库的当前时间戳。所以，需要数据库和机器的时间要一致。
                .eq(FranchiseeEntity::getId,franchiseeId)
                .eq(FranchiseeEntity::getStoreNum,dbStoreNum));//乐观锁更新。
        if(result <= 0){//更新失败，说明数据已经被修改了，重新查询一次，在次更新，直到数据没有被别的线程修改（最新数据）。
            return queryFranchiseeStoreNumAndUpdate(franchiseeId);
        }
        return storeInfoDetailVOS;
    }

    @Override
    public List<StoreInfoDetailVO> queryFranchiseeStoreNumAndUpdate(String franchiseeId) {
        FranchiseeEntity franchiseeEntity = getEntityById(franchiseeId);
        Map<String, List<StoreInfoDetailVO>> storeBaseListInfos = organizationHelper.buildStoreInfoDetailsByFranchiseeId(franchiseeId);
        ServiceException.isTrue(CollectionUtil.isNotEmpty(storeBaseListInfos),"查询异常，请稍后再试!");
        List<StoreInfoDetailVO> storeInfoDetailVOS = storeBaseListInfos.get(franchiseeId);
        return updateFranchiseeStoreNum(franchiseeEntity,storeInfoDetailVOS);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String generateCode() {
        LocalDate today = LocalDate.now();
        String datePart = FRANCHISEE_CODE_DATE_FORMATTER.format(today);
        String todaySeqKey = buildFranchiseeCodeSeqKey(today);

        Long sequence = mysqlSequenceHelper.increment(todaySeqKey,FRANCHISEE_CODE_SEQ_KEY_PREFIX);
        if(sequence >= FRANCHISEE_CODE_MAX_SEQUENCE){
            throw new RuntimeException("加盟商编号已超出最大值!");
        }
        long serialValue = sequence == null || sequence < 1 ? 1L : sequence;
        if (serialValue == 1L) {
            baseParamMapper.deleteByTypeNotKey(FRANCHISEE_CODE_SEQ_KEY_PREFIX,todaySeqKey);
        }

        String serialPart = serialValue <= 999 ? String.format("%03d", serialValue) : String.valueOf(serialValue);
        return FRANCHISEE_CODE_PREFIX + datePart + serialPart;
    }

    @Override
    public List<FranchiseeIdName> getFranchiseeIdNameListByIds(Collection<String> ids) {
        return baseMapper.selectList(new LambdaQueryWrapper<FranchiseeEntity>()
                .eq(FranchiseeEntity::getEnabledMark, 0)
                .in(FranchiseeEntity::getId,ids))
                .stream()
                .map(e->new FranchiseeIdName(e.getId(),e.getFranchiseeName(),e.getMobilePhone(),e.getFranchiseeCode()))
                .collect(Collectors.toList());
    }

    @Override
    public List<FranchiseeIdName> getFranchiseeIdNameListByNames(Collection<String> names) {
        return baseMapper.selectList(new LambdaQueryWrapper<FranchiseeEntity>()
                        .eq(FranchiseeEntity::getEnabledMark, 0)
                        .in(FranchiseeEntity::getFranchiseeName,names))
                .stream()
                .map(e->new FranchiseeIdName(e.getId(),e.getFranchiseeName(),e.getMobilePhone(),e.getFranchiseeCode()))
                .collect(Collectors.toList());
    }

    @Override
    public List<FranchiseeIdName> getFranchiseeIdNameListByCodes(Collection<String> codes) {
        return baseMapper.selectList(new LambdaQueryWrapper<FranchiseeEntity>()
                        .eq(FranchiseeEntity::getEnabledMark, 0)
                        .in(FranchiseeEntity::getFranchiseeCode,codes))
                .stream()
                .map(e->new FranchiseeIdName(e.getId(),e.getFranchiseeName(),e.getMobilePhone(),e.getFranchiseeCode()))
                .collect(Collectors.toList());
    }

    /**
     * 校验新增/编辑参数，并返回规范化后的自定义字段列表
     *
     * @param req 请求参数
     * @return 规范化后的自定义字段列表
     */
    private List<FranchiseeCustomFieldReq> validateAddReq(FranchiseeAddReq req) {
        if (req == null) {
            throw new RuntimeException("请求参数不能为空");
        }
        if (StrUtil.isBlank(req.getFranchiseeCode())) {
            throw new RuntimeException("加盟商编号不能为空");
        }
        if (StrUtil.isBlank(req.getFranchiseeName())) {
            throw new RuntimeException("加盟商名称不能为空");
        }
        if (StrUtil.isBlank(req.getMobilePhone())) {
            throw new RuntimeException("手机号不能为空");
        }
        if (req.getAge() != null && req.getAge() < 0) {
            throw new RuntimeException("年龄不能小于0");
        }
        if (req.getInvestStoreCount() != null && req.getInvestStoreCount() < 0) {
            throw new RuntimeException("投资门店数不能小于0");
        }
        if (req.getExpectedStaffCount() != null && req.getExpectedStaffCount() < 0) {
            throw new RuntimeException("预计员工数不能小于0");
        }

        List<FranchiseeCustomFieldReq> customFieldList = normalizeAndValidateCustomFieldList(req.getCustomFieldList());
        validateExperience(req.getExperienceList());
        return customFieldList;
    }

    /**
     * 校验分页查询参数
     *
     * @param req 查询参数
     */
    private void validateQueryReq(FranchiseeQueryReq req) {
        if (req.getMinStoreNum() != null && req.getMaxStoreNum() != null
                && req.getMinStoreNum() > req.getMaxStoreNum()) {
            throw new RuntimeException("最小门店数不能大于最大门店数");
        }
        if (req.getMinInvestAmount() != null && req.getMaxInvestAmount() != null
                && req.getMinInvestAmount().compareTo(req.getMaxInvestAmount()) > 0) {
            throw new RuntimeException("最小投资金额不能大于最大投资金额");
        }
        if (req.getJoinDateStart() != null && req.getJoinDateEnd() != null
                && req.getJoinDateStart().after(req.getJoinDateEnd())) {
            throw new RuntimeException("加盟日期开始时间不能晚于结束时间");
        }

        Integer joinDurationStart = req.getJoinDurationStart();
        Integer joinDurationEnd = req.getJoinDurationEnd();
        if (joinDurationStart != null && joinDurationEnd != null
                && joinDurationStart > joinDurationEnd) {
            throw new RuntimeException("加盟时长开始不能大于结束");
        }
        if(joinDurationStart != null &&  joinDurationStart < 0){
            throw new RuntimeException("加盟时长开始不能小于0");
        }
        if(joinDurationEnd != null &&  joinDurationEnd < 0){
            throw new RuntimeException("加盟时长结束不能小于0");
        }
        boolean joinDateQuery = req.getJoinDateStart() != null || req.getJoinDateEnd() != null;
        if(joinDurationStart != null && !joinDateQuery){
            Date joinDateEnd = DateUtil.dateAddMonths(cn.hutool.core.date.DateUtil.beginOfDay(new Date()), -joinDurationStart);
            req.setJoinDateEnd(joinDateEnd);
        }
        if(joinDurationEnd != null && !joinDateQuery){
            Date joinDateStart = DateUtil.dateAddMonths(cn.hutool.core.date.DateUtil.beginOfDay(new Date()), -joinDurationEnd);
            req.setJoinDateStart(joinDateStart);
        }
    }

    /**
     * 校验并规范化自定义字段列表
     *
     * @param customFieldList 自定义字段列表
     * @return 规范化后的列表
     */
    private List<FranchiseeCustomFieldReq> validateCustomField(List<FranchiseeCustomFieldReq> customFieldList) {
        if (CollUtil.isEmpty(customFieldList)) {
            return Collections.emptyList();
        }

        Set<String> nameSet = new LinkedHashSet<>();
        List<FranchiseeCustomFieldReq> result = new ArrayList<>();
        for (FranchiseeCustomFieldReq item : customFieldList) {
            if (item == null) {
                continue;
            }
            String fieldName = StrUtil.trim(item.getFieldName());
            if (StrUtil.isBlank(fieldName)) {
                throw new RuntimeException("自定义字段名称不能为空");
            }
            if (!nameSet.add(fieldName)) {
                throw new RuntimeException("自定义字段名称不能重复");
            }

            FranchiseeCustomFieldReq normalizedItem = new FranchiseeCustomFieldReq();
            normalizedItem.setFieldName(fieldName);
            normalizedItem.setFieldValue(StrUtil.trim(item.getFieldValue()));
            result.add(normalizedItem);
        }
        return result;
    }

    /**
     * 解析并校验自定义字段 JSON 字符串
     *
     * @param customFieldList 自定义字段 JSON 字符串
     * @return 规范化后的自定义字段列表
     */
    private List<FranchiseeCustomFieldReq> normalizeAndValidateCustomFieldList(String customFieldList) {
        String json = StrUtil.blankToDefault(customFieldList, EMPTY_JSON_ARRAY).trim();
        if (!JSONUtil.isTypeJSONArray(json)) {
            throw new RuntimeException("自定义字段列表必须为JSON数组");
        }
        try {
            List<FranchiseeCustomFieldReq> list = JSONUtil.toList(JSONUtil.parseArray(json), FranchiseeCustomFieldReq.class);
            return validateCustomField(list);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("自定义字段列表必须为JSON数组");
        }
    }

    /**
     * 校验从业经历
     *
     * @param experienceList 从业经历列表
     */
    private void validateExperience(List<FranchiseeExperienceReq> experienceList) {
        if (CollUtil.isEmpty(experienceList)) {
            return;
        }
        if (experienceList.size() > MAX_EXPERIENCE_COUNT) {
            throw new RuntimeException("从业经历最多填写5条");
        }

        for (FranchiseeExperienceReq item : experienceList) {
            if (item == null) {
                continue;
            }
            Date startTime = item.getStartTime();
            Date endTime = item.getEndTime();
            if (startTime != null && endTime != null && startTime.after(endTime)) {
                throw new RuntimeException("从业经历开始时间不能晚于结束时间");
            }
        }
    }

    /**
     * 根据ID查询有效加盟商
     *
     * @param id 主键ID
     * @return 加盟商实体
     */
    private FranchiseeEntity getEntityById(String id) {
        FranchiseeEntity entity = baseMapper.selectById(id);
        if (entity == null || !Integer.valueOf(0).equals(entity.getEnabledMark())) {
            throw new RuntimeException("加盟商不存在");
        }
        return entity;
    }

    /**
     * 根据加盟商ID统计门店真实数量
     *
     * @param franchiseeId 加盟商ID
     * @return 门店数量
     */
    private int queryRealStoreNum(String franchiseeId) {
        Map<String, List<StoreInfoDetailVO>> storeInfoDetailsByFranchiseeId = organizationHelper.buildStoreInfoDetailsByFranchiseeId(franchiseeId);
        if(Objects.isNull(storeInfoDetailsByFranchiseeId)){
            throw new RuntimeException("组织架构服务异常!");
        }
        return storeInfoDetailsByFranchiseeId.size();
    }

    /**
     * 删除自定义字段子表数据
     *
     * @param franchiseeId 加盟商ID
     */
    private void deleteCustomFieldByFranchiseeId(String franchiseeId) {
        LambdaQueryWrapper<FranchiseeCustomFieldEntity> deleteWrapper = Wrappers.lambdaQuery();
        deleteWrapper.eq(FranchiseeCustomFieldEntity::getFranchiseeId, franchiseeId);
        franchiseeCustomFieldMapper.delete(deleteWrapper);
    }

    /**
     * 保存自定义字段子表数据
     *
     * @param franchiseeId       加盟商ID
     * @param customFieldReqList 自定义字段列表
     */
    private void saveCustomField(String franchiseeId, List<FranchiseeCustomFieldReq> customFieldReqList) {
        if (CollUtil.isEmpty(customFieldReqList)) {
            return;
        }

        List<FranchiseeCustomFieldEntity> entities = new ArrayList<>(customFieldReqList.size());
        long sorts = 1L;
        for (FranchiseeCustomFieldReq item : customFieldReqList) {
            if (item == null) {
                continue;
            }
            FranchiseeCustomFieldEntity entity = new FranchiseeCustomFieldEntity();
            entity.setId(FtbUtil.getId());
            entity.setFranchiseeId(franchiseeId);
            entity.setFieldName(StrUtil.trim(item.getFieldName()));
            entity.setFieldValue(StrUtil.trim(item.getFieldValue()));
            entity.setSorts(sorts++);
            entity.setEnabledMark(0);
            entities.add(entity);
        }
        if (CollUtil.isNotEmpty(entities)) {
            franchiseeCustomFieldMapper.batchInsert(entities);
        }
    }

    /**
     * 查询自定义字段值列表
     *
     * @param franchiseeId 加盟商ID
     * @return 自定义字段值列表
     */
    private List<FranchiseeCustomFieldVO> queryCustomFieldList(String franchiseeId) {
        LambdaQueryWrapper<FranchiseeCustomFieldEntity> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(FranchiseeCustomFieldEntity::getFranchiseeId, franchiseeId);
        queryWrapper.eq(FranchiseeCustomFieldEntity::getEnabledMark, 0);
        queryWrapper.orderByAsc(FranchiseeCustomFieldEntity::getSorts);

        List<FranchiseeCustomFieldEntity> entityList = franchiseeCustomFieldMapper.selectList(queryWrapper);
        if (CollUtil.isEmpty(entityList)) {
            return Collections.emptyList();
        }
        return entityList.stream().map(FranchiseeCustomFieldVO::convert).collect(Collectors.toList());
    }

    /**
     * 删除从业经历子表数据
     *
     * @param franchiseeId 加盟商ID
     */
    private void deleteExperienceByFranchiseeId(String franchiseeId) {
        LambdaQueryWrapper<FranchiseeExperienceEntity> deleteWrapper = Wrappers.lambdaQuery();
        deleteWrapper.eq(FranchiseeExperienceEntity::getFranchiseeId, franchiseeId);
        franchiseeExperienceMapper.delete(deleteWrapper);
    }

    /**
     * 删除加盟地区冗余数据。
     *
     * @param franchiseeId 加盟商ID
     */
    private void deleteJoinRegionByFranchiseeId(String franchiseeId) {
        LambdaQueryWrapper<FranchiseeJoinRegionEntity> deleteWrapper = Wrappers.lambdaQuery();
        deleteWrapper.eq(FranchiseeJoinRegionEntity::getFranchiseeId, franchiseeId);
        franchiseeJoinRegionMapper.delete(deleteWrapper);
    }

    /**
     * 保存加盟地区冗余数据。
     *
     * @param franchiseeId 加盟商ID
     * @param joinRegionReqs 加盟地区列表
     */
    private void saveJoinRegion(String franchiseeId, List<FranchiseeJoinRegionReq> joinRegionReqs) {
        if (CollUtil.isEmpty(joinRegionReqs)) {
            return;
        }
        List<FranchiseeJoinRegionEntity> entities = new ArrayList<>(joinRegionReqs.size());
        for (FranchiseeJoinRegionReq item : joinRegionReqs) {
            if (item == null) {
                continue;
            }
            FranchiseeJoinRegionEntity entity = new FranchiseeJoinRegionEntity();
            entity.setId(FtbUtil.getId());
            entity.setFranchiseeId(franchiseeId);
            entity.setProvince(item.getProvince());
            entity.setCity(item.getCity());
            entity.setDistrict(item.getDistrict());
            entity.setCounty(item.getCounty());
            entities.add(entity);
        }
        if (CollUtil.isNotEmpty(entities)) {
            franchiseeJoinRegionMapper.batchInsert(entities);
        }
    }

    /**
     * 保存从业经历
     *
     * @param franchiseeId   加盟商ID
     * @param experienceReqs 从业经历列表
     */
    private void saveExperience(String franchiseeId, List<FranchiseeExperienceReq> experienceReqs) {
        if (CollUtil.isEmpty(experienceReqs)) {
            return;
        }

        List<FranchiseeExperienceEntity> entities = new ArrayList<>(experienceReqs.size());
        long sorts = 1L;
        for (FranchiseeExperienceReq item : experienceReqs) {
            if (item == null || isEmptyExperienceItem(item)) {
                continue;
            }

            FranchiseeExperienceEntity entity = new FranchiseeExperienceEntity();
            entity.setId(FtbUtil.getId());
            entity.setFranchiseeId(franchiseeId);
            entity.setCompanyName(StrUtil.trim(item.getCompanyName()));
            entity.setPositionName(StrUtil.trim(item.getPositionName()));
            entity.setStartTime(item.getStartTime());
            entity.setEndTime(item.getEndTime());
            entity.setIndustry(StrUtil.trim(item.getIndustry()));
            entity.setWorkDescription(StrUtil.trim(item.getWorkDescription()));
            entity.setSorts(sorts++);
            entity.setEnabledMark(0);
            entities.add(entity);
        }
        if (CollUtil.isNotEmpty(entities)) {
            franchiseeExperienceMapper.batchInsert(entities);
        }
    }

    /**
     * 判断从业经历是否为空行
     *
     * @param item 从业经历
     * @return true-空行，false-非空行
     */
    private boolean isEmptyExperienceItem(FranchiseeExperienceReq item) {
        return StrUtil.isBlank(item.getCompanyName())
                && StrUtil.isBlank(item.getPositionName())
                && item.getStartTime() == null
                && item.getEndTime() == null
                && StrUtil.isBlank(item.getIndustry())
                && StrUtil.isBlank(item.getWorkDescription());
    }

    /**
     * 查询从业经历列表
     *
     * @param franchiseeId 加盟商ID
     * @return 从业经历返回列表
     */
    private List<FranchiseeExperienceVO> queryExperienceList(String franchiseeId) {
        LambdaQueryWrapper<FranchiseeExperienceEntity> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(FranchiseeExperienceEntity::getFranchiseeId, franchiseeId);
        queryWrapper.eq(FranchiseeExperienceEntity::getEnabledMark, 0);
        queryWrapper.orderByAsc(FranchiseeExperienceEntity::getSorts);

        List<FranchiseeExperienceEntity> entityList = franchiseeExperienceMapper.selectList(queryWrapper);
        if (CollUtil.isEmpty(entityList)) {
            return Collections.emptyList();
        }
        return entityList.stream().map(FranchiseeExperienceVO::convert).collect(Collectors.toList());
    }

    /**
     * 新增加盟商主表
     *
     * @param entity 加盟商主表实体
     */
    private void insertFranchiseeEntity(FranchiseeEntity entity) {
        try {
            baseMapper.insert(entity);
        } catch (DuplicateKeyException e) {
            if(containsDuplicateKey(e,"uk_franchisee_code")){
                throw new RuntimeException("加盟商编号已存在");
            }
            if(containsDuplicateKey(e,"uk_franchisee_name")){
                throw new RuntimeException("加盟商名称已存在");
            }
            throw new RuntimeException("加盟商编号或者加盟商名称已存在");
        }
    }

    private boolean containsDuplicateKey(DuplicateKeyException e, String ukIndexName) {
        if(Objects.isNull(e)){
            return false;
        }
        if(StringUtil.isBlank(ukIndexName)){
            return false;
        }
        String message = e.getMessage();
        if(StringUtil.isBlank(message)){
            return false;
        }
        return message.contains(ukIndexName);
    }

    /**
     * 更新加盟商主表
     *
     * @param entity 加盟商主表实体
     */
    private void updateFranchiseeEntity(FranchiseeEntity entity) {
        try {
            baseMapper.updateById(entity);
        } catch (DuplicateKeyException e) {
            if(containsDuplicateKey(e,"uk_franchisee_code")){
                throw new RuntimeException("加盟商编号已存在");
            }
            if(containsDuplicateKey(e,"uk_franchisee_name")){
                throw new RuntimeException("加盟商名称已存在");
            }
            throw new RuntimeException("加盟商编号或者加盟商名称已存在");
        }
    }

    /**
     * 规范化加盟地区列表。
     *
     * @param joinRegionReqs 加盟地区列表
     * @return 规范化后的加盟地区列表
     */
    private List<FranchiseeJoinRegionReq> normalizeJoinRegionList(List<FranchiseeJoinRegionReq> joinRegionReqs) {
        if (CollUtil.isEmpty(joinRegionReqs)) {
            return Collections.emptyList();
        }
        Set<String> uniqueKeys = new LinkedHashSet<>();
        List<FranchiseeJoinRegionReq> result = new ArrayList<>();
        for (FranchiseeJoinRegionReq item : joinRegionReqs) {
            FranchiseeJoinRegionReq normalized = normalizeJoinRegionItem(item);
            if (normalized == null) {
                continue;
            }
            String uniqueKey = buildJoinRegionUniqueKey(normalized);
            if (!uniqueKeys.add(uniqueKey)) {
                continue;
            }
            result.add(normalized);
        }
        return result;
    }

    /**
     * 查询条件扩展：县补省市区/省市/省，区补省市/省，市补省。
     *
     * @param joinRegionReqs 标准化后的加盟地区列表
     * @return 扩展后的加盟地区列表
     */
    private List<FranchiseeJoinRegionReq> expandQueryJoinRegionList(List<FranchiseeJoinRegionReq> joinRegionReqs) {
        if (CollUtil.isEmpty(joinRegionReqs)) {
            return Collections.emptyList();
        }

        Set<String> uniqueKeys = new LinkedHashSet<>();
        List<FranchiseeJoinRegionReq> result = new ArrayList<>();
        for (FranchiseeJoinRegionReq item : joinRegionReqs) {
            appendJoinRegion(result, uniqueKeys, item.getProvince(), item.getCity(), item.getDistrict(), item.getCounty());
            if (StrUtil.isNotBlank(item.getCounty())) {
                appendJoinRegion(result, uniqueKeys, item.getProvince(), item.getCity(), item.getDistrict(), null);
                appendJoinRegion(result, uniqueKeys, item.getProvince(), item.getCity(), null, null);
                appendJoinRegion(result, uniqueKeys, item.getProvince(), null, null, null);
                continue;
            }
            if (StrUtil.isNotBlank(item.getDistrict())) {
                appendJoinRegion(result, uniqueKeys, item.getProvince(), item.getCity(), null, null);
                appendJoinRegion(result, uniqueKeys, item.getProvince(), null, null, null);
                continue;
            }
            if (StrUtil.isNotBlank(item.getCity())) {
                appendJoinRegion(result, uniqueKeys, item.getProvince(), null, null, null);
            }
        }
        return result;
    }

    /**
     * 添加加盟地区到结果集合并去重。
     *
     * @param result 查询地区结果
     * @param uniqueKeys 去重Key
     * @param province 省
     * @param city 市
     * @param district 区
     * @param county 县
     */
    private void appendJoinRegion(List<FranchiseeJoinRegionReq> result, Set<String> uniqueKeys,
                                  String province, String city, String district, String county) {
        FranchiseeJoinRegionReq source = new FranchiseeJoinRegionReq();
        source.setProvince(province);
        source.setCity(city);
        source.setDistrict(district);
        source.setCounty(county);

        FranchiseeJoinRegionReq normalized = normalizeJoinRegionItem(source);
        if (normalized == null) {
            return;
        }
        String uniqueKey = buildJoinRegionUniqueKey(normalized);
        if (!uniqueKeys.add(uniqueKey)) {
            return;
        }
        result.add(normalized);
    }

    /**
     * 标准化加盟地区单条记录。
     *
     * @param item 加盟地区
     * @return 标准化后的记录
     */
    private FranchiseeJoinRegionReq normalizeJoinRegionItem(FranchiseeJoinRegionReq item) {
        if (item == null) {
            return null;
        }

        String province = StrUtil.trim(item.getProvince());
        String city = StrUtil.trim(item.getCity());
        String district = StrUtil.trim(item.getDistrict());
        String county = StrUtil.trim(item.getCounty());
        if (StrUtil.isBlank(province)) {
            return null;
        }

        if (StrUtil.isBlank(city)) {
            city = null;
            district = null;
            county = null;
        } else if (StrUtil.isBlank(district)) {
            district = null;
            county = null;
        } else if (StrUtil.isBlank(county)) {
            county = null;
        }

        FranchiseeJoinRegionReq normalized = new FranchiseeJoinRegionReq();
        normalized.setProvince(province);
        normalized.setCity(city);
        normalized.setDistrict(district);
        normalized.setCounty(county);
        return normalized;
    }

    /**
     * 加盟地区去重Key。
     *
     * @param item 加盟地区
     * @return 去重Key
     */
    private String buildJoinRegionUniqueKey(FranchiseeJoinRegionReq item) {
        return item.getProvince()
                + "#"
                + StrUtil.blankToDefault(item.getCity(), "")
                + "#"
                + StrUtil.blankToDefault(item.getDistrict(), "")
                + "#"
                + StrUtil.blankToDefault(item.getCounty(), "");
    }

    /**
     * 提取“区为空”时的省市集合。
     *
     * @param joinRegionReqs 标准化后的加盟地区
     * @return 省市集合
     */
    private List<FranchiseeJoinRegionReq> extractProvinceCityJoinRegion(List<FranchiseeJoinRegionReq> joinRegionReqs) {
        if (CollUtil.isEmpty(joinRegionReqs)) {
            return Collections.emptyList();
        }

        Set<String> uniqueKeys = new LinkedHashSet<>();
        List<FranchiseeJoinRegionReq> result = new ArrayList<>();
        for (FranchiseeJoinRegionReq item : joinRegionReqs) {
            if (StrUtil.isBlank(item.getDistrict()) && StrUtil.isNotBlank(item.getCity())) {
                String key = item.getProvince() + "#" + item.getCity();
                if (!uniqueKeys.add(key)) {
                    continue;
                }
                FranchiseeJoinRegionReq provinceCity = new FranchiseeJoinRegionReq();
                provinceCity.setProvince(item.getProvince());
                provinceCity.setCity(item.getCity());
                result.add(provinceCity);
            }
        }
        return result;
    }

    /**
     * 提取“市为空”时的省集合。
     *
     * @param joinRegionReqs 标准化后的加盟地区
     * @return 省集合
     */
    private List<String> extractProvinceList(List<FranchiseeJoinRegionReq> joinRegionReqs) {
        if (CollUtil.isEmpty(joinRegionReqs)) {
            return Collections.emptyList();
        }

        Set<String> provinceSet = new LinkedHashSet<>();
        for (FranchiseeJoinRegionReq item : joinRegionReqs) {
            if (StrUtil.isBlank(item.getCity())) {
                provinceSet.add(item.getProvince());
            }
        }
        return new ArrayList<>(provinceSet);
    }

    /**
     * 生成加盟商编码的key
     * @param date
     * @return
     */
    private String buildFranchiseeCodeSeqKey(LocalDate date) {
        return FRANCHISEE_CODE_SEQ_KEY_PREFIX + FRANCHISEE_CODE_DATE_FORMATTER.format(date);
    }

    private Long createDailySequenceWhenAbsent(String key) {
        FtbParamEntity entity = new FtbParamEntity();
        entity.setId(RandomUtil.uuId());
        entity.setKey(key);
        entity.setType(key);
        entity.setValue("1");
        entity.setSort(1L);

        try {
            baseParamMapper.insertParam(entity);
            return 1L;
        } catch (DuplicateKeyException e) {
            return incrementDailySequence(key);
        }
    }

    private Long incrementDailySequence(String key) {
        int updateCount = baseParamMapper.incrementValueByKey(key, 1L);
        if (updateCount == 0) {
            return createDailySequenceWhenAbsent(key);
        }

        FtbParamEntity entity = baseParamMapper.selectByKey(key);
        if (entity == null || StrUtil.isBlank(entity.getValue())) {
            return 1L;
        }
        try {
            return Long.parseLong(StrUtil.trim(entity.getValue()));
        } catch (NumberFormatException e) {
            return 1L;
        }
    }

    private FranchiseeStoreVO convertStoreVO(StoreInfoDetailVO storeBaseInfo,
                                             Map<String, UserBoundVO> userBoundVOMap,
                                             Map<String,List<CertificateInstanceEntity>> certificateMap) {
        FranchiseeStoreVO vo = new FranchiseeStoreVO();
        String storeId = storeBaseInfo.getId();
        vo.setStoreId(storeId);
        vo.setStoreName(StrUtil.trim(storeBaseInfo.getStoreName()));
        vo.setDisabled(storeBaseInfo.getDisabled());
        vo.setAreaNum(storeBaseInfo.getAreaNum());
        vo.setLongitude(StrUtil.trim(storeBaseInfo.getLongitude()));
        vo.setLatitude(StrUtil.trim(storeBaseInfo.getLatitude()));
        vo.setAddress(StrUtil.trim(storeBaseInfo.getAddress()));
        vo.setUserNum(storeBaseInfo.getUserNum());
        vo.setOrganizeTreeName(storeBaseInfo.getOrganizeName());

        String storeHeadUserId = storeBaseInfo.getStoreHeadUserId();
        UserBoundVO userBoundVO = userBoundVOMap.get(storeHeadUserId);
        if(Objects.nonNull(userBoundVO)){
            vo.setStoreHeadUserName(userBoundVO.getName());
            vo.setStoreHeadUserId(storeHeadUserId);
        }

        String longitude = StrUtil.blankToDefault(storeBaseInfo.getLongitude(), "");
        String latitude = StrUtil.blankToDefault(storeBaseInfo.getLatitude(), "");
        if (StrUtil.isAllBlank(longitude, latitude)) {
            vo.setCoordinate(null);
        } else {
            vo.setCoordinate(longitude + "," + latitude);
        }

        List<StoreRegionVO> regionList = storeBaseInfo.getRegionList();
        if(CollectionUtil.isNotEmpty(regionList)){
            StoreRegionVO storeRegionVO = regionList.get(0);
            vo.setDeskCount(storeRegionVO.getDeskCount());
            vo.setSeatCount(storeRegionVO.getSeatCount());
        }

        List<CertificateInstanceEntity> certificateInstanceEntities = certificateMap.get(storeId);
        vo.setBusinessLicenseStatus(1);
        vo.setBusinessLicenseStatusName(CertificateStatusUtils.buildStatusName(1));
        vo.setHygieneLicenseStatus(1);
        vo.setHygieneLicenseStatusName(CertificateStatusUtils.buildStatusName(1));

        if(CollectionUtil.isEmpty(certificateInstanceEntities)){
            return vo;
        }
        CertificateInstanceEntity businessLicense = null;
        CertificateInstanceEntity hygieneLicense = null;
        for (CertificateInstanceEntity certificateInstanceEntity : certificateInstanceEntities){
            String certificateType = certificateInstanceEntity.getCertificateType();
            if(CertificateTypeEnum.BUSINESS_LICENSE.getType().equals(certificateType)){
                businessLicense = certificateInstanceEntity;
            } else if (CertificateTypeEnum.HYGIENE_LICENSE.getType().equals(certificateType)) {
                hygieneLicense = certificateInstanceEntity;
            }
        }
        if(Objects.nonNull(businessLicense)){
            Integer status = businessLicense.getStatus();
            vo.setBusinessLicenseStatus(status);
            vo.setBusinessLicenseStatusName(CertificateStatusUtils.buildStatusName(status));
        }
        if(Objects.nonNull(hygieneLicense)){
            Integer status = hygieneLicense.getStatus();
            vo.setHygieneLicenseStatus(status);
            vo.setHygieneLicenseStatusName(CertificateStatusUtils.buildStatusName(status));
        }
        return vo;
    }
}
