package org.dromara.art.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.idev.excel.FastExcel;
import cn.idev.excel.ExcelWriter;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.excel.convert.ExcelBigNumberConvert;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.art.domain.bo.ArtSchoolBo;
import org.dromara.art.domain.bo.ArtSchoolDetailBo;
import org.dromara.art.domain.bo.ArtSchoolSubmitBo;
import org.dromara.art.domain.excel.ArtSchoolCampusSheetExcel;
import org.dromara.art.domain.excel.ArtSchoolCollegeSheetExcel;
import org.dromara.art.domain.excel.ArtSchoolDetailSheetExcel;
import org.dromara.art.domain.excel.ArtSchoolEnrollPlanSheetExcel;
import org.dromara.art.domain.excel.ArtSchoolMajorSheetExcel;
import org.dromara.art.domain.excel.ArtSchoolSheetExcel;
import org.dromara.art.domain.excel.ArtSchoolTemplateGuideSheetExcel;
import org.dromara.art.domain.vo.ArtSchoolImportDetailVo;
import org.dromara.art.domain.vo.ArtSchoolImportPreviewVo;
import org.dromara.art.domain.vo.ArtSchoolImportResultVo;
import org.dromara.art.domain.vo.ArtSchoolVo;
import org.dromara.art.domain.vo.ArtSchoolSubmitResultVo;
import org.dromara.art.domain.ArtSchool;
import org.dromara.art.domain.ArtSchoolCampus;
import org.dromara.art.domain.ArtSchoolCollege;
import org.dromara.art.domain.ArtSchoolDetail;
import org.dromara.art.domain.ArtSchoolEnrollCode;
import org.dromara.art.domain.ArtSchoolEnrollPlan;
import org.dromara.art.domain.ArtSchoolMajor;
import org.dromara.art.domain.ArtSchoolTag;
import org.dromara.art.mapper.ArtSchoolCampusMapper;
import org.dromara.art.mapper.ArtSchoolCollegeMapper;
import org.dromara.art.mapper.ArtSchoolDetailMapper;
import org.dromara.art.mapper.ArtSchoolEnrollCodeMapper;
import org.dromara.art.mapper.ArtSchoolEnrollPlanMapper;
import org.dromara.art.mapper.ArtSchoolMajorMapper;
import org.dromara.art.mapper.ArtSchoolMapper;
import org.dromara.art.mapper.ArtSchoolTagMapper;
import org.dromara.art.service.IArtSchoolService;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 学校基础信息主表Service业务层处理
 *
 * @author ZhouWenTao
 * @date 2026-02-17
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class ArtSchoolServiceImpl implements IArtSchoolService {

    private static final String SHEET_SCHOOL = "学校基本信息";
    private static final String SHEET_DETAIL = "学校详情信息";
    private static final String SHEET_CAMPUS = "校区信息";
    private static final String SHEET_COLLEGE = "学院信息";
    private static final String SHEET_MAJOR = "专业信息";
    private static final String SHEET_ENROLL_PLAN = "招生计划信息";
    private static final String SHEET_GUIDE = "模板说明";

    private final ArtSchoolMapper baseMapper;
    private final ArtSchoolDetailMapper artSchoolDetailMapper;
    private final ArtSchoolEnrollCodeMapper artSchoolEnrollCodeMapper;
    private final ArtSchoolTagMapper artSchoolTagMapper;
    private final ArtSchoolCampusMapper artSchoolCampusMapper;
    private final ArtSchoolCollegeMapper artSchoolCollegeMapper;
    private final ArtSchoolMajorMapper artSchoolMajorMapper;
    private final ArtSchoolEnrollPlanMapper artSchoolEnrollPlanMapper;
    private final TransactionTemplate transactionTemplate;

    /**
     * 查询学校基础信息主表
     *
     * @param schoolId 主键
     * @return 学校基础信息主表
     */
    @Override
    public ArtSchoolVo queryById(Long schoolId){
        ArtSchoolVo schoolVo = baseMapper.selectVoById(schoolId);
        if (schoolVo == null) {
            return null;
        }
        fillEnrollCodes(List.of(schoolVo));
        fillSchoolTags(List.of(schoolVo));
        return schoolVo;
    }

    /**
     * 分页查询学校基础信息主表列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 学校基础信息主表分页列表
     */
    @Override
    public TableDataInfo<ArtSchoolVo> queryPageList(ArtSchoolBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<ArtSchool> lqw = buildQueryWrapper(bo);
        Page<ArtSchoolVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        fillEnrollCodes(result.getRecords());
        fillSchoolTags(result.getRecords());
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的学校基础信息主表列表
     *
     * @param bo 查询条件
     * @return 学校基础信息主表列表
     */
    @Override
    public List<ArtSchoolVo> queryList(ArtSchoolBo bo) {
        LambdaQueryWrapper<ArtSchool> lqw = buildQueryWrapper(bo);
        List<ArtSchoolVo> schoolVos = baseMapper.selectVoList(lqw);
        fillEnrollCodes(schoolVos);
        fillSchoolTags(schoolVos);
        return schoolVos;
    }

    @Override
    public void exportTemplateData(ArtSchoolBo bo, HttpServletResponse response) {
        LambdaQueryWrapper<ArtSchool> lqw = buildQueryWrapper(bo);
        List<ArtSchool> schools = baseMapper.selectList(lqw);
        exportWorkbook(schools, response);
    }

    @Override
    public void exportImportTemplate(HttpServletResponse response) {
        exportWorkbook(Collections.emptyList(), response);
    }

    @Override
    public ArtSchoolImportPreviewVo previewImport(MultipartFile file) {
        SchoolWorkbookData workbookData = readWorkbook(file);
        List<ArtSchoolImportDetailVo> details = new ArrayList<>();

        WorkbookValidateResult validateResult = validateWorkbook(workbookData);
        details.addAll(validateResult.getInvalidDetails());

        List<ArtSchoolSheetExcel> schools = workbookData.getSchools();
        for (ArtSchoolSheetExcel schoolRow : schools) {
            if (hasInvalidSchool(validateResult, schoolRow)) {
                continue;
            }
            ArtSchool exists = selectExistSchool(schoolRow.getMainCode(), schoolRow.getMainName());
            if (exists != null) {
                ArtSchoolImportDetailVo conflict = new ArtSchoolImportDetailVo();
                conflict.setMainCode(schoolRow.getMainCode());
                conflict.setMainName(schoolRow.getMainName());
                conflict.setStatus("CONFLICT");
                conflict.setMessage("系统已存在该学校数据，导入时需确认是否替换");
                details.add(conflict);
            }
        }

        ArtSchoolImportPreviewVo previewVo = new ArtSchoolImportPreviewVo();
        previewVo.setTotalSchoolCount(schools.size());
        previewVo.setConflictCount((int) details.stream().filter(d -> "CONFLICT".equals(d.getStatus())).count());
        previewVo.setInvalidCount((int) details.stream().filter(d -> "INVALID".equals(d.getStatus())).count());
        previewVo.setDetails(details);
        return previewVo;
    }

    @Override
    public ArtSchoolImportResultVo importData(MultipartFile file, List<String> replaceMainCodes, boolean replaceAll) {
        SchoolWorkbookData workbookData = readWorkbook(file);
        WorkbookValidateResult validateResult = validateWorkbook(workbookData);
        Set<String> replaceCodeSet = CollUtil.isEmpty(replaceMainCodes) ? Collections.emptySet() : new HashSet<>(replaceMainCodes);

        List<ArtSchoolImportDetailVo> details = new ArrayList<>(validateResult.getInvalidDetails());
        int successCount = 0;
        int failCount = validateResult.getInvalidDetails().size();
        int skippedCount = 0;

        for (ArtSchoolSheetExcel schoolRow : workbookData.getSchools()) {
            if (hasInvalidSchool(validateResult, schoolRow)) {
                continue;
            }

            String mainCode = trimToEmpty(schoolRow.getMainCode());
            String mainName = trimToEmpty(schoolRow.getMainName());
            ArtSchool exists = selectExistSchool(mainCode, mainName);
            if (exists != null && !replaceAll && !replaceCodeSet.contains(mainCode)) {
                ArtSchoolImportDetailVo skipped = new ArtSchoolImportDetailVo();
                skipped.setMainCode(mainCode);
                skipped.setMainName(mainName);
                skipped.setStatus("SKIPPED");
                skipped.setMessage("学校名称重复且用户取消替换");
                details.add(skipped);
                skippedCount++;
                continue;
            }

            try {
                Boolean txResult = transactionTemplate.execute(status -> {
                    upsertSchoolWithChildren(workbookData, schoolRow, exists, replaceAll || replaceCodeSet.contains(mainCode));
                    return Boolean.TRUE;
                });
                if (!Boolean.TRUE.equals(txResult)) {
                    throw new ServiceException("事务执行失败");
                }
                ArtSchoolImportDetailVo success = new ArtSchoolImportDetailVo();
                success.setMainCode(mainCode);
                success.setMainName(mainName);
                success.setStatus("SUCCESS");
                success.setMessage(exists == null ? "导入成功" : "替换成功");
                details.add(success);
                successCount++;
            } catch (Exception e) {
                ArtSchoolImportDetailVo failed = new ArtSchoolImportDetailVo();
                failed.setMainCode(mainCode);
                failed.setMainName(mainName);
                failed.setStatus("FAILED");
                failed.setMessage(StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : "导入失败");
                details.add(failed);
                failCount++;
            }
        }

        ArtSchoolImportResultVo resultVo = new ArtSchoolImportResultVo();
        resultVo.setTotalSchoolCount(workbookData.getSchools().size());
        resultVo.setSuccessCount(successCount);
        resultVo.setFailCount(failCount);
        resultVo.setSkippedCount(skippedCount);
        resultVo.setDetails(details);
        return resultVo;
    }

    private LambdaQueryWrapper<ArtSchool> buildQueryWrapper(ArtSchoolBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<ArtSchool> lqw = Wrappers.lambdaQuery();
        lqw.orderByAsc(ArtSchool::getSchoolId);
        lqw.eq(StringUtils.isNotBlank(bo.getMainCode()), ArtSchool::getMainCode, bo.getMainCode());
        lqw.like(StringUtils.isNotBlank(bo.getMainName()), ArtSchool::getMainName, bo.getMainName());
        lqw.like(StringUtils.isNotBlank(bo.getShortName()), ArtSchool::getShortName, bo.getShortName());
        lqw.eq(StringUtils.isNotBlank(bo.getSchoolIcon()), ArtSchool::getSchoolIcon, bo.getSchoolIcon());
        lqw.eq(StringUtils.isNotBlank(bo.getProvince()), ArtSchool::getProvince, bo.getProvince());
        lqw.eq(StringUtils.isNotBlank(bo.getCity()), ArtSchool::getCity, bo.getCity());
        lqw.eq(StringUtils.isNotBlank(bo.getDistrict()), ArtSchool::getDistrict, bo.getDistrict());
        lqw.eq(StringUtils.isNotBlank(bo.getUniversityType()), ArtSchool::getUniversityType, bo.getUniversityType());
        lqw.eq(StringUtils.isNotBlank(bo.getEducationLevel()), ArtSchool::getEducationLevel, bo.getEducationLevel());
        lqw.eq(StringUtils.isNotBlank(bo.getSchoolNature()), ArtSchool::getSchoolNature, bo.getSchoolNature());
        lqw.eq(StringUtils.isNotBlank(bo.getSupervisorDept()), ArtSchool::getSupervisorDept, bo.getSupervisorDept());
        lqw.eq(bo.getEstablishYear() != null, ArtSchool::getEstablishYear, bo.getEstablishYear());
        lqw.eq(bo.getCampusAreaMu() != null, ArtSchool::getCampusAreaMu, bo.getCampusAreaMu());
        lqw.eq(bo.getMaleRatio() != null, ArtSchool::getMaleRatio, bo.getMaleRatio());
        lqw.eq(bo.getFemaleRatio() != null, ArtSchool::getFemaleRatio, bo.getFemaleRatio());
        return lqw;
    }

    /**
     * 新增学校基础信息主表
     *
     * @param bo 学校基础信息主表
     * @return 是否新增成功
     */
    @Override
    public Boolean insertByBo(ArtSchoolBo bo) {
        ArtSchool add = MapstructUtils.convert(bo, ArtSchool.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setSchoolId(add.getSchoolId());
        }
        return flag;
    }

    /**
     * 新增学校基础信息与详情信息（事务）
     *
     * @param bo 统一提交对象
     * @return 处理结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ArtSchoolSubmitResultVo insertWithDetailByBo(ArtSchoolSubmitBo bo) {
        if (bo == null || bo.getSchool() == null || bo.getDetail() == null) {
            throw new ServiceException("学校基础信息和详情信息不能为空");
        }

        ArtSchoolBo schoolBo = bo.getSchool();
        ArtSchoolDetailBo detailBo = bo.getDetail();
        ValidatorUtils.validate(schoolBo, AddGroup.class);

        ArtSchool school = MapstructUtils.convert(schoolBo, ArtSchool.class);
        validEntityBeforeSave(school);
        boolean schoolFlag = baseMapper.insert(school) > 0;
        if (!schoolFlag) {
            throw new ServiceException("学校基础信息保存失败");
        }
        schoolBo.setSchoolId(school.getSchoolId());

        if (detailBo.getSchoolId() != null && !Objects.equals(detailBo.getSchoolId(), school.getSchoolId())) {
            throw new ServiceException("学校详情schoolId与主表schoolId不一致");
        }
        detailBo.setSchoolId(school.getSchoolId());
        ValidatorUtils.validate(detailBo, AddGroup.class);

        ArtSchoolDetail detail = MapstructUtils.convert(detailBo, ArtSchoolDetail.class);
        boolean detailFlag = saveOrUpdateDetailBySchoolId(detail);
        if (!detailFlag) {
            throw new ServiceException("学校详情信息保存失败");
        }
        detailBo.setDetailId(detail.getDetailId());

        boolean enrollCodeFlag = saveOrUpdateEnrollCodesBySchoolId(school.getSchoolId(), bo.getEnrollCodes());
        if (!enrollCodeFlag) {
            throw new ServiceException("学校招生代码保存失败");
        }
        boolean schoolTagFlag = saveOrUpdateSchoolTagsBySchoolId(school.getSchoolId(), bo.getSchoolTags());
        if (!schoolTagFlag) {
            throw new ServiceException("学校标签保存失败");
        }

        ArtSchoolSubmitResultVo resultVo = new ArtSchoolSubmitResultVo();
        resultVo.setSchoolId(school.getSchoolId());
        resultVo.setDetailId(detail.getDetailId());
        resultVo.setSchoolSuccess(Boolean.TRUE);
        resultVo.setDetailSuccess(Boolean.TRUE);
        resultVo.setEnrollCodeSuccess(Boolean.TRUE);
        resultVo.setSchoolTagSuccess(Boolean.TRUE);
        return resultVo;
    }

    /**
     * 修改学校基础信息与详情信息（事务）
     *
     * @param bo 统一提交对象
     * @return 处理结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ArtSchoolSubmitResultVo updateWithDetailByBo(ArtSchoolSubmitBo bo) {
        if (bo == null || bo.getSchool() == null || bo.getDetail() == null) {
            throw new ServiceException("学校基础信息和详情信息不能为空");
        }

        ArtSchoolBo schoolBo = bo.getSchool();
        ArtSchoolDetailBo detailBo = bo.getDetail();
        ValidatorUtils.validate(schoolBo, EditGroup.class);

        ArtSchool school = MapstructUtils.convert(schoolBo, ArtSchool.class);
        validEntityBeforeSave(school);
        boolean schoolFlag = baseMapper.updateById(school) > 0;
        if (!schoolFlag) {
            throw new ServiceException("学校基础信息更新失败");
        }

        if (detailBo.getSchoolId() != null && !Objects.equals(detailBo.getSchoolId(), schoolBo.getSchoolId())) {
            throw new ServiceException("学校详情schoolId与主表schoolId不一致");
        }
        detailBo.setSchoolId(schoolBo.getSchoolId());
        ValidatorUtils.validate(detailBo, AddGroup.class);

        ArtSchoolDetail detail = MapstructUtils.convert(detailBo, ArtSchoolDetail.class);
        boolean detailFlag = saveOrUpdateDetailBySchoolId(detail);
        if (!detailFlag) {
            throw new ServiceException("学校详情信息更新失败");
        }
        detailBo.setDetailId(detail.getDetailId());

        boolean enrollCodeFlag = saveOrUpdateEnrollCodesBySchoolId(schoolBo.getSchoolId(), bo.getEnrollCodes());
        if (!enrollCodeFlag) {
            throw new ServiceException("学校招生代码更新失败");
        }
        boolean schoolTagFlag = saveOrUpdateSchoolTagsBySchoolId(schoolBo.getSchoolId(), bo.getSchoolTags());
        if (!schoolTagFlag) {
            throw new ServiceException("学校标签更新失败");
        }

        ArtSchoolSubmitResultVo resultVo = new ArtSchoolSubmitResultVo();
        resultVo.setSchoolId(schoolBo.getSchoolId());
        resultVo.setDetailId(detail.getDetailId());
        resultVo.setSchoolSuccess(Boolean.TRUE);
        resultVo.setDetailSuccess(Boolean.TRUE);
        resultVo.setEnrollCodeSuccess(Boolean.TRUE);
        resultVo.setSchoolTagSuccess(Boolean.TRUE);
        return resultVo;
    }

    /**
     * 修改学校基础信息主表
     *
     * @param bo 学校基础信息主表
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(ArtSchoolBo bo) {
        ArtSchool update = MapstructUtils.convert(bo, ArtSchool.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(ArtSchool entity){
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 详情信息按 schoolId 进行新增或更新（保障一校一详情的提交体验）
     */
    private boolean saveOrUpdateDetailBySchoolId(ArtSchoolDetail detail) {
        if (detail.getDetailId() != null) {
            boolean updated = artSchoolDetailMapper.updateById(detail) > 0;
            if (updated) {
                return true;
            }
        }
        LambdaQueryWrapper<ArtSchoolDetail> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(ArtSchoolDetail::getSchoolId, detail.getSchoolId());
        ArtSchoolDetail exist = artSchoolDetailMapper.selectOne(queryWrapper, false);
        if (exist == null) {
            return artSchoolDetailMapper.insert(detail) > 0;
        }
        detail.setDetailId(exist.getDetailId());
        return artSchoolDetailMapper.updateById(detail) > 0;
    }

    /**
     * 招生代码按 schoolId 全量替换
     */
    private boolean saveOrUpdateEnrollCodesBySchoolId(Long schoolId, List<String> enrollCodes) {
        if (enrollCodes == null) {
            return true;
        }
        artSchoolEnrollCodeMapper.deletePhyBySchoolId(schoolId);
        if (CollUtil.isEmpty(enrollCodes)) {
            return true;
        }
        Set<String> uniqueCodes = new LinkedHashSet<>();
        for (String code : enrollCodes) {
            String enrollCode = trimToEmpty(code);
            if (StringUtils.isBlank(enrollCode)) {
                throw new ServiceException("学校招生代码不能为空");
            }
            if (!uniqueCodes.add(enrollCode)) {
                continue;
            }

            ArtSchoolEnrollCode enrollCodeEntity = new ArtSchoolEnrollCode();
            enrollCodeEntity.setSchoolId(schoolId);
            enrollCodeEntity.setEnrollCode(enrollCode);
            boolean insertFlag = artSchoolEnrollCodeMapper.insert(enrollCodeEntity) > 0;
            if (!insertFlag) {
                return false;
            }
        }
        return true;
    }

    /**
     * 学校标签按 schoolId 全量替换
     */
    private boolean saveOrUpdateSchoolTagsBySchoolId(Long schoolId, List<String> schoolTags) {
        if (schoolTags == null) {
            return true;
        }
        artSchoolTagMapper.deletePhyBySchoolId(schoolId);
        if (CollUtil.isEmpty(schoolTags)) {
            return true;
        }
        Set<String> uniqueTags = new LinkedHashSet<>();
        for (String tag : schoolTags) {
            String tagName = trimToEmpty(tag);
            if (StringUtils.isBlank(tagName)) {
                throw new ServiceException("学校标签不能为空");
            }
            if (!uniqueTags.add(tagName)) {
                continue;
            }
            ArtSchoolTag schoolTag = new ArtSchoolTag();
            schoolTag.setSchoolId(schoolId);
            schoolTag.setTagName(tagName);
            boolean insertFlag = artSchoolTagMapper.insert(schoolTag) > 0;
            if (!insertFlag) {
                return false;
            }
        }
        return true;
    }

    private void fillEnrollCodes(List<ArtSchoolVo> schoolVos) {
        if (CollUtil.isEmpty(schoolVos)) {
            return;
        }
        List<Long> schoolIds = schoolVos.stream()
            .map(ArtSchoolVo::getSchoolId)
            .filter(Objects::nonNull)
            .toList();
        if (CollUtil.isEmpty(schoolIds)) {
            return;
        }

        List<ArtSchoolEnrollCode> enrollCodeEntities = artSchoolEnrollCodeMapper.selectList(
            Wrappers.<ArtSchoolEnrollCode>lambdaQuery()
                .in(ArtSchoolEnrollCode::getSchoolId, schoolIds)
                .orderByAsc(ArtSchoolEnrollCode::getId));
        Map<Long, List<String>> enrollCodeMap = enrollCodeEntities.stream()
            .collect(Collectors.groupingBy(ArtSchoolEnrollCode::getSchoolId,
                LinkedHashMap::new,
                Collectors.mapping(ArtSchoolEnrollCode::getEnrollCode, Collectors.toList())));
        for (ArtSchoolVo schoolVo : schoolVos) {
            schoolVo.setEnrollCodes(enrollCodeMap.getOrDefault(schoolVo.getSchoolId(), Collections.emptyList()));
        }
    }

    private void fillSchoolTags(List<ArtSchoolVo> schoolVos) {
        if (CollUtil.isEmpty(schoolVos)) {
            return;
        }
        List<Long> schoolIds = schoolVos.stream()
            .map(ArtSchoolVo::getSchoolId)
            .filter(Objects::nonNull)
            .toList();
        if (CollUtil.isEmpty(schoolIds)) {
            return;
        }

        List<ArtSchoolTag> schoolTagEntities = artSchoolTagMapper.selectList(
            Wrappers.<ArtSchoolTag>lambdaQuery()
                .in(ArtSchoolTag::getSchoolId, schoolIds)
                .orderByAsc(ArtSchoolTag::getSchoolTagId));
        Map<Long, List<String>> schoolTagMap = schoolTagEntities.stream()
            .collect(Collectors.groupingBy(ArtSchoolTag::getSchoolId,
                LinkedHashMap::new,
                Collectors.mapping(ArtSchoolTag::getTagName, Collectors.toList())));
        for (ArtSchoolVo schoolVo : schoolVos) {
            schoolVo.setSchoolTags(schoolTagMap.getOrDefault(schoolVo.getSchoolId(), Collections.emptyList()));
        }
    }

    private void exportWorkbook(List<ArtSchool> schools, HttpServletResponse response) {
        try {
            FileUtils.setAttachmentResponseHeader(response, "学校导入导出模板.xlsx");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
            OutputStream os = response.getOutputStream();
            writeWorkbook(os, schools);
        } catch (IOException e) {
            throw new ServiceException("导出Excel异常: {}", e.getMessage());
        }
    }

    private void writeWorkbook(OutputStream outputStream, List<ArtSchool> schools) {
        List<Long> schoolIds = schools.stream().map(ArtSchool::getSchoolId).collect(Collectors.toList());
        Map<Long, ArtSchool> schoolMap = schools.stream().collect(Collectors.toMap(ArtSchool::getSchoolId, s -> s, (v1, v2) -> v1, LinkedHashMap::new));

        List<ArtSchoolDetail> details = CollUtil.isEmpty(schoolIds) ? Collections.emptyList()
            : artSchoolDetailMapper.selectList(Wrappers.<ArtSchoolDetail>lambdaQuery().in(ArtSchoolDetail::getSchoolId, schoolIds));
        List<ArtSchoolCampus> campuses = CollUtil.isEmpty(schoolIds) ? Collections.emptyList()
            : artSchoolCampusMapper.selectList(Wrappers.<ArtSchoolCampus>lambdaQuery().in(ArtSchoolCampus::getSchoolId, schoolIds));
        List<ArtSchoolCollege> colleges = CollUtil.isEmpty(schoolIds) ? Collections.emptyList()
            : artSchoolCollegeMapper.selectList(Wrappers.<ArtSchoolCollege>lambdaQuery().in(ArtSchoolCollege::getSchoolId, schoolIds));
        List<ArtSchoolMajor> majors = CollUtil.isEmpty(schoolIds) ? Collections.emptyList()
            : artSchoolMajorMapper.selectList(Wrappers.<ArtSchoolMajor>lambdaQuery().in(ArtSchoolMajor::getSchoolId, schoolIds));
        List<ArtSchoolEnrollPlan> plans = CollUtil.isEmpty(schoolIds) ? Collections.emptyList()
            : artSchoolEnrollPlanMapper.selectList(Wrappers.<ArtSchoolEnrollPlan>lambdaQuery().in(ArtSchoolEnrollPlan::getSchoolId, schoolIds));

        List<ArtSchoolSheetExcel> schoolRows = schools.stream().map(this::toSchoolSheet).toList();
        List<ArtSchoolDetailSheetExcel> detailRows = details.stream().map(detail -> toDetailSheet(detail, schoolMap.get(detail.getSchoolId()))).toList();
        List<ArtSchoolCampusSheetExcel> campusRows = campuses.stream().map(campus -> toCampusSheet(campus, schoolMap.get(campus.getSchoolId()))).toList();
        List<ArtSchoolCollegeSheetExcel> collegeRows = colleges.stream().map(college -> toCollegeSheet(college, schoolMap.get(college.getSchoolId()))).toList();
        Map<Long, ArtSchoolCollege> collegeMap = colleges.stream()
            .collect(Collectors.toMap(ArtSchoolCollege::getCollegeId, c -> c, (v1, v2) -> v1, LinkedHashMap::new));
        List<ArtSchoolMajorSheetExcel> majorRows = majors.stream()
            .map(major -> toMajorSheet(major, schoolMap.get(major.getSchoolId()), collegeMap.get(major.getCollegeId())))
            .toList();
        List<ArtSchoolEnrollPlanSheetExcel> planRows = plans.stream().map(plan -> toEnrollPlanSheet(plan, schoolMap.get(plan.getSchoolId()))).toList();

        List<ArtSchoolTemplateGuideSheetExcel> guideRows = buildGuideRows();
        try (ExcelWriter writer = FastExcel.write(outputStream)
            .autoCloseStream(false)
            .registerConverter(new ExcelBigNumberConvert())
            .build()) {
            writer.write(schoolRows, FastExcel.writerSheet(0, SHEET_SCHOOL).head(ArtSchoolSheetExcel.class).build());
            writer.write(detailRows, FastExcel.writerSheet(1, SHEET_DETAIL).head(ArtSchoolDetailSheetExcel.class).build());
            writer.write(campusRows, FastExcel.writerSheet(2, SHEET_CAMPUS).head(ArtSchoolCampusSheetExcel.class).build());
            writer.write(collegeRows, FastExcel.writerSheet(3, SHEET_COLLEGE).head(ArtSchoolCollegeSheetExcel.class).build());
            writer.write(majorRows, FastExcel.writerSheet(4, SHEET_MAJOR).head(ArtSchoolMajorSheetExcel.class).build());
            writer.write(planRows, FastExcel.writerSheet(5, SHEET_ENROLL_PLAN).head(ArtSchoolEnrollPlanSheetExcel.class).build());
            writer.write(guideRows, FastExcel.writerSheet(6, SHEET_GUIDE).head(ArtSchoolTemplateGuideSheetExcel.class).build());
        }
    }

    private SchoolWorkbookData readWorkbook(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new ServiceException("导入文件不能为空");
        }
        SchoolWorkbookData data = new SchoolWorkbookData();
        data.setSchools(readSheet(file, SHEET_SCHOOL, ArtSchoolSheetExcel.class, true));
        data.setDetails(readSheet(file, SHEET_DETAIL, ArtSchoolDetailSheetExcel.class, false));
        data.setCampuses(readSheet(file, SHEET_CAMPUS, ArtSchoolCampusSheetExcel.class, false));
        data.setColleges(readSheet(file, SHEET_COLLEGE, ArtSchoolCollegeSheetExcel.class, false));
        data.setMajors(readSheet(file, SHEET_MAJOR, ArtSchoolMajorSheetExcel.class, false));
        data.setPlans(readSheet(file, SHEET_ENROLL_PLAN, ArtSchoolEnrollPlanSheetExcel.class, false));
        if (CollUtil.isEmpty(data.getSchools())) {
            throw new ServiceException("导入失败：学校基本信息sheet不能为空");
        }
        return data;
    }

    private <T> List<T> readSheet(MultipartFile file, String sheetName, Class<T> headClazz, boolean required) {
        InputStream is = null;
        try {
            is = file.getInputStream();
            return FastExcel.read(is).head(headClazz).autoCloseStream(false).sheet(sheetName).doReadSync();
        } catch (Exception e) {
            if (required) {
                throw new ServiceException("导入失败：未找到sheet[{}]或数据格式不正确", sheetName);
            }
            return Collections.emptyList();
        } finally {
            IoUtil.close(is);
        }
    }

    private WorkbookValidateResult validateWorkbook(SchoolWorkbookData workbookData) {
        List<ArtSchoolImportDetailVo> invalidDetails = new ArrayList<>();
        Set<String> invalidSchoolKeys = new HashSet<>();
        Set<String> seenCode = new HashSet<>();
        Set<String> seenName = new HashSet<>();
        Set<String> schoolKeySet = new HashSet<>();

        for (ArtSchoolSheetExcel school : workbookData.getSchools()) {
            String mainCode = trimToEmpty(school.getMainCode());
            String mainName = trimToEmpty(school.getMainName());
            String key = schoolKey(mainCode, mainName);
            boolean invalid = false;
            if (StringUtils.isBlank(mainCode) || StringUtils.isBlank(mainName)) {
                invalid = true;
                invalidDetails.add(buildInvalidDetail(mainCode, mainName, "必填字段缺失：学校编码/学校名称不能为空"));
            } else if (!seenCode.add(mainCode)) {
                invalid = true;
                invalidDetails.add(buildInvalidDetail(mainCode, mainName, "文件内学校编码重复"));
            } else if (!seenName.add(mainName)) {
                invalid = true;
                invalidDetails.add(buildInvalidDetail(mainCode, mainName, "文件内学校名称重复"));
            }
            if (invalid) {
                invalidSchoolKeys.add(key);
            } else {
                schoolKeySet.add(key);
            }
        }

        validateChildRows(workbookData.getDetails(), schoolKeySet, invalidDetails, "学校详情");
        validateChildRows(workbookData.getCampuses(), schoolKeySet, invalidDetails, "校区信息");
        validateChildRows(workbookData.getColleges(), schoolKeySet, invalidDetails, "学院信息");
        validateChildRows(workbookData.getMajors(), schoolKeySet, invalidDetails, "专业信息");
        validateChildRows(workbookData.getPlans(), schoolKeySet, invalidDetails, "招生计划");

        WorkbookValidateResult result = new WorkbookValidateResult();
        result.setInvalidDetails(invalidDetails);
        result.setInvalidSchoolKeys(invalidSchoolKeys);
        return result;
    }

    private <T> void validateChildRows(List<T> childRows, Set<String> schoolKeySet, List<ArtSchoolImportDetailVo> invalidDetails, String sheetLabel) {
        if (CollUtil.isEmpty(childRows)) {
            return;
        }
        for (T row : childRows) {
            String code = null;
            String name = null;
            if (row instanceof ArtSchoolDetailSheetExcel detailRow) {
                code = detailRow.getSchoolMainCode();
                name = detailRow.getSchoolMainName();
            } else if (row instanceof ArtSchoolCampusSheetExcel campusRow) {
                code = campusRow.getSchoolMainCode();
                name = campusRow.getSchoolMainName();
            } else if (row instanceof ArtSchoolCollegeSheetExcel collegeRow) {
                code = collegeRow.getSchoolMainCode();
                name = collegeRow.getSchoolMainName();
            } else if (row instanceof ArtSchoolMajorSheetExcel majorRow) {
                code = majorRow.getSchoolMainCode();
                name = majorRow.getSchoolMainName();
            } else if (row instanceof ArtSchoolEnrollPlanSheetExcel planRow) {
                code = planRow.getSchoolMainCode();
                name = planRow.getSchoolMainName();
            }
            String key = schoolKey(trimToEmpty(code), trimToEmpty(name));
            if (StringUtils.isBlank(code) || StringUtils.isBlank(name)) {
                invalidDetails.add(buildInvalidDetail(code, name, sheetLabel + "关联字段缺失：学校编码/学校名称不能为空"));
            } else if (!schoolKeySet.contains(key)) {
                invalidDetails.add(buildInvalidDetail(code, name, sheetLabel + "关联学校不存在于主表sheet"));
            }
        }
    }

    private ArtSchoolImportDetailVo buildInvalidDetail(String mainCode, String mainName, String message) {
        ArtSchoolImportDetailVo detailVo = new ArtSchoolImportDetailVo();
        detailVo.setMainCode(mainCode);
        detailVo.setMainName(mainName);
        detailVo.setStatus("INVALID");
        detailVo.setMessage(message);
        return detailVo;
    }

    private boolean hasInvalidSchool(WorkbookValidateResult validateResult, ArtSchoolSheetExcel schoolRow) {
        return validateResult.getInvalidSchoolKeys().contains(schoolKey(trimToEmpty(schoolRow.getMainCode()), trimToEmpty(schoolRow.getMainName())));
    }

    private void upsertSchoolWithChildren(SchoolWorkbookData workbookData, ArtSchoolSheetExcel schoolRow, ArtSchool exists, boolean replace) {
        if (exists != null && !replace) {
            throw new ServiceException("学校已存在且未开启替换");
        }
        String mainCode = trimToEmpty(schoolRow.getMainCode());
        if (exists != null) {
            deleteSchoolWithChildren(exists.getSchoolId());
        } else if (StringUtils.isNotBlank(mainCode)) {
            // 兜底清理历史逻辑删除残留数据，避免唯一索引(main_code)冲突
            Long deletedSchoolId = baseMapper.selectSchoolIdIgnoreLogicByMainCode(mainCode);
            if (deletedSchoolId != null) {
                deleteSchoolWithChildren(deletedSchoolId);
            }
        }

        ArtSchool school = toSchoolEntity(schoolRow);
        school.setSchoolId(null);
        if (baseMapper.insert(school) <= 0) {
            throw new ServiceException("学校主表保存失败");
        }
        Long newSchoolId = school.getSchoolId();

        for (ArtSchoolDetailSheetExcel row : workbookData.getDetails()) {
            if (!sameSchool(row.getSchoolMainCode(), row.getSchoolMainName(), schoolRow.getMainCode(), schoolRow.getMainName())) {
                continue;
            }
            ArtSchoolDetail detail = toDetailEntity(row, newSchoolId);
            detail.setDetailId(null);
            if (artSchoolDetailMapper.insert(detail) <= 0) {
                throw new ServiceException("学校详情保存失败");
            }
        }

        for (ArtSchoolCampusSheetExcel row : workbookData.getCampuses()) {
            if (!sameSchool(row.getSchoolMainCode(), row.getSchoolMainName(), schoolRow.getMainCode(), schoolRow.getMainName())) {
                continue;
            }
            if (StringUtils.isBlank(row.getCampusName())) {
                throw new ServiceException("校区名称不能为空");
            }
            ArtSchoolCampus campus = toCampusEntity(row, newSchoolId);
            campus.setCampusId(null);
            if (artSchoolCampusMapper.insert(campus) <= 0) {
                throw new ServiceException("校区保存失败");
            }
        }

        Map<Long, Long> oldCollegeIdToNewCollegeId = new HashMap<>();
        Map<String, Long> collegeBizKeyToNewCollegeId = new HashMap<>();
        for (ArtSchoolCollegeSheetExcel row : workbookData.getColleges()) {
            if (!sameSchool(row.getSchoolMainCode(), row.getSchoolMainName(), schoolRow.getMainCode(), schoolRow.getMainName())) {
                continue;
            }
            if (StringUtils.isBlank(row.getCollegeName())) {
                throw new ServiceException("学院名称不能为空");
            }
            Long oldCollegeId = row.getCollegeId();
            ArtSchoolCollege college = toCollegeEntity(row, newSchoolId);
            college.setCollegeId(null);
            if (artSchoolCollegeMapper.insert(college) <= 0) {
                throw new ServiceException("学院保存失败");
            }
            if (oldCollegeId != null) {
                oldCollegeIdToNewCollegeId.put(oldCollegeId, college.getCollegeId());
            }
            collegeBizKeyToNewCollegeId.put(collegeKey(row.getCollegeCode(), row.getCollegeName()), college.getCollegeId());
            collegeBizKeyToNewCollegeId.putIfAbsent(collegeKey(null, row.getCollegeName()), college.getCollegeId());
        }

        Map<Long, Long> oldMajorIdToNewMajorId = new HashMap<>();
        Map<String, Long> majorBizKeyToNewMajorId = new HashMap<>();
        for (ArtSchoolMajorSheetExcel row : workbookData.getMajors()) {
            if (!sameSchool(row.getSchoolMainCode(), row.getSchoolMainName(), schoolRow.getMainCode(), schoolRow.getMainName())) {
                continue;
            }
            if (!hasCollegeBindingInfo(row) || StringUtils.isBlank(row.getMajorName())) {
                throw new ServiceException("专业信息必填字段缺失：学院ID或学院编码/学院名称，且专业名称不能为空");
            }
            Long resolvedCollegeId = resolveCollegeId(row, oldCollegeIdToNewCollegeId, collegeBizKeyToNewCollegeId);
            if (resolvedCollegeId == null) {
                throw new ServiceException("专业关联学院不存在：专业[" + trimToEmpty(row.getMajorName()) + "]");
            }
            ArtSchoolMajor major = toMajorEntity(row, newSchoolId, resolvedCollegeId);
            major.setMajorId(null);
            Long oldMajorId = row.getMajorId();
            if (artSchoolMajorMapper.insert(major) <= 0) {
                throw new ServiceException("专业保存失败");
            }
            if (oldMajorId != null) {
                oldMajorIdToNewMajorId.put(oldMajorId, major.getMajorId());
            }
            majorBizKeyToNewMajorId.put(majorKey(row.getMajorCode(), row.getMajorName(), row.getEducationLevel()), major.getMajorId());
            majorBizKeyToNewMajorId.putIfAbsent(majorKey(null, row.getMajorName(), row.getEducationLevel()), major.getMajorId());
        }

        for (ArtSchoolEnrollPlanSheetExcel row : workbookData.getPlans()) {
            if (!sameSchool(row.getSchoolMainCode(), row.getSchoolMainName(), schoolRow.getMainCode(), schoolRow.getMainName())) {
                continue;
            }
            if (row.getYear() == null || StringUtils.isBlank(row.getProvince()) || StringUtils.isBlank(row.getMajorName()) || row.getPlanNum() == null) {
                throw new ServiceException("招生计划必填字段缺失：年份/招生省份/专业名称/计划数");
            }
            Long resolvedMajorId = resolveMajorId(row, oldMajorIdToNewMajorId, majorBizKeyToNewMajorId);
            ArtSchoolEnrollPlan plan = toPlanEntity(row, newSchoolId, resolvedMajorId);
            plan.setPlanId(null);
            if (artSchoolEnrollPlanMapper.insert(plan) <= 0) {
                throw new ServiceException("招生计划保存失败");
            }
        }
    }

    private void deleteSchoolWithChildren(Long schoolId) {
        artSchoolDetailMapper.deletePhyBySchoolId(schoolId);
        artSchoolEnrollCodeMapper.deletePhyBySchoolId(schoolId);
        artSchoolTagMapper.deletePhyBySchoolId(schoolId);
        artSchoolCampusMapper.deletePhyBySchoolId(schoolId);
        artSchoolCollegeMapper.deletePhyBySchoolId(schoolId);
        artSchoolMajorMapper.deletePhyBySchoolId(schoolId);
        artSchoolEnrollPlanMapper.deletePhyBySchoolId(schoolId);
        baseMapper.deletePhyBySchoolId(schoolId);
    }

    private ArtSchool selectExistSchool(String mainCode, String mainName) {
        if (StringUtils.isNotBlank(mainCode)) {
            ArtSchool byCode = baseMapper.selectOne(Wrappers.<ArtSchool>lambdaQuery()
                .eq(ArtSchool::getMainCode, mainCode)
                .last("limit 1"), false);
            if (byCode != null) {
                return byCode;
            }
        }
        if (StringUtils.isNotBlank(mainName)) {
            return baseMapper.selectOne(Wrappers.<ArtSchool>lambdaQuery()
                .eq(ArtSchool::getMainName, mainName)
                .last("limit 1"), false);
        }
        return null;
    }

    private ArtSchoolSheetExcel toSchoolSheet(ArtSchool school) {
        ArtSchoolSheetExcel row = new ArtSchoolSheetExcel();
        row.setSchoolId(school.getSchoolId());
        row.setMainCode(school.getMainCode());
        row.setMainName(school.getMainName());
        row.setShortName(school.getShortName());
        row.setSchoolIcon(school.getSchoolIcon());
        row.setProvince(school.getProvince());
        row.setCity(school.getCity());
        row.setDistrict(school.getDistrict());
        row.setUniversityType(school.getUniversityType());
        row.setEducationLevel(school.getEducationLevel());
        row.setSchoolNature(school.getSchoolNature());
        row.setSupervisorDept(school.getSupervisorDept());
        row.setEstablishYear(school.getEstablishYear());
        row.setCampusAreaMu(school.getCampusAreaMu());
        row.setMaleRatio(school.getMaleRatio());
        row.setFemaleRatio(school.getFemaleRatio());
        row.setRemark(school.getRemark());
        return row;
    }

    private ArtSchoolDetailSheetExcel toDetailSheet(ArtSchoolDetail detail, ArtSchool school) {
        ArtSchoolDetailSheetExcel row = new ArtSchoolDetailSheetExcel();
        row.setDetailId(detail.getDetailId());
        if (school != null) {
            row.setSchoolMainCode(school.getMainCode());
            row.setSchoolMainName(school.getMainName());
        }
        row.setIntroduction(detail.getIntroduction());
        row.setAddress(detail.getAddress());
        row.setContact(detail.getContact());
        row.setEmail(detail.getEmail());
        row.setWebsite(detail.getWebsite());
        row.setRemark(detail.getRemark());
        return row;
    }

    private ArtSchoolCampusSheetExcel toCampusSheet(ArtSchoolCampus campus, ArtSchool school) {
        ArtSchoolCampusSheetExcel row = new ArtSchoolCampusSheetExcel();
        row.setCampusId(campus.getCampusId());
        if (school != null) {
            row.setSchoolMainCode(school.getMainCode());
            row.setSchoolMainName(school.getMainName());
        }
        row.setCampusName(campus.getCampusName());
        row.setLocation(campus.getLocation());
        row.setAddress(campus.getAddress());
        row.setLng(campus.getLng());
        row.setLat(campus.getLat());
        row.setIntroduction(campus.getIntroduction());
        row.setRemark(campus.getRemark());
        return row;
    }

    private ArtSchoolCollegeSheetExcel toCollegeSheet(ArtSchoolCollege college, ArtSchool school) {
        ArtSchoolCollegeSheetExcel row = new ArtSchoolCollegeSheetExcel();
        row.setCollegeId(college.getCollegeId());
        if (school != null) {
            row.setSchoolMainCode(school.getMainCode());
            row.setSchoolMainName(school.getMainName());
        }
        row.setCollegeCode(college.getCollegeCode());
        row.setCollegeName(college.getCollegeName());
        row.setIntroduction(college.getIntroduction());
        row.setSortNo(college.getSortNo());
        row.setRemark(college.getRemark());
        return row;
    }

    private ArtSchoolMajorSheetExcel toMajorSheet(ArtSchoolMajor major, ArtSchool school, ArtSchoolCollege college) {
        ArtSchoolMajorSheetExcel row = new ArtSchoolMajorSheetExcel();
        row.setMajorId(major.getMajorId());
        if (school != null) {
            row.setSchoolMainCode(school.getMainCode());
            row.setSchoolMainName(school.getMainName());
        }
        row.setCollegeId(major.getCollegeId());
        if (college != null) {
            row.setCollegeCode(college.getCollegeCode());
            row.setCollegeName(college.getCollegeName());
        }
        row.setMajorCode(major.getMajorCode());
        row.setMajorName(major.getMajorName());
        row.setEducationLevel(major.getEducationLevel());
        row.setDurationYears(major.getDurationYears());
        row.setMajorCategory(major.getMajorCategory());
        row.setDegreeType(major.getDegreeType());
        row.setIntroduction(major.getIntroduction());
        row.setTags(major.getTags());
        row.setRemark(major.getRemark());
        return row;
    }

    private ArtSchoolEnrollPlanSheetExcel toEnrollPlanSheet(ArtSchoolEnrollPlan plan, ArtSchool school) {
        ArtSchoolEnrollPlanSheetExcel row = new ArtSchoolEnrollPlanSheetExcel();
        row.setPlanId(plan.getPlanId());
        if (school != null) {
            row.setSchoolMainCode(school.getMainCode());
            row.setSchoolMainName(school.getMainName());
        }
        row.setYear(plan.getYear());
        row.setProvince(plan.getProvince());
        row.setSubjectType(plan.getSubjectType());
        row.setMajorId(plan.getMajorId());
        row.setMajorName(plan.getMajorName());
        row.setEducationLevel(plan.getEducationLevel());
        row.setPlanNum(plan.getPlanNum());
        row.setRemark(plan.getRemark());
        return row;
    }

    private ArtSchool toSchoolEntity(ArtSchoolSheetExcel row) {
        ArtSchool school = new ArtSchool();
        school.setMainCode(trimToEmpty(row.getMainCode()));
        school.setMainName(trimToEmpty(row.getMainName()));
        school.setShortName(row.getShortName());
        school.setSchoolIcon(row.getSchoolIcon());
        school.setProvince(row.getProvince());
        school.setCity(row.getCity());
        school.setDistrict(row.getDistrict());
        school.setUniversityType(row.getUniversityType());
        school.setEducationLevel(row.getEducationLevel());
        school.setSchoolNature(row.getSchoolNature());
        school.setSupervisorDept(row.getSupervisorDept());
        school.setEstablishYear(row.getEstablishYear());
        school.setCampusAreaMu(row.getCampusAreaMu());
        school.setMaleRatio(row.getMaleRatio());
        school.setFemaleRatio(row.getFemaleRatio());
        school.setRemark(row.getRemark());
        return school;
    }

    private ArtSchoolDetail toDetailEntity(ArtSchoolDetailSheetExcel row, Long schoolId) {
        ArtSchoolDetail detail = new ArtSchoolDetail();
        detail.setSchoolId(schoolId);
        detail.setIntroduction(row.getIntroduction());
        detail.setAddress(row.getAddress());
        detail.setContact(row.getContact());
        detail.setEmail(row.getEmail());
        detail.setWebsite(row.getWebsite());
        detail.setRemark(row.getRemark());
        return detail;
    }

    private ArtSchoolCampus toCampusEntity(ArtSchoolCampusSheetExcel row, Long schoolId) {
        ArtSchoolCampus campus = new ArtSchoolCampus();
        campus.setSchoolId(schoolId);
        campus.setCampusName(row.getCampusName());
        campus.setLocation(row.getLocation());
        campus.setAddress(row.getAddress());
        campus.setLng(row.getLng());
        campus.setLat(row.getLat());
        campus.setIntroduction(row.getIntroduction());
        campus.setRemark(row.getRemark());
        return campus;
    }

    private ArtSchoolCollege toCollegeEntity(ArtSchoolCollegeSheetExcel row, Long schoolId) {
        ArtSchoolCollege college = new ArtSchoolCollege();
        college.setSchoolId(schoolId);
        college.setCollegeCode(row.getCollegeCode());
        college.setCollegeName(row.getCollegeName());
        college.setIntroduction(row.getIntroduction());
        college.setSortNo(row.getSortNo());
        college.setRemark(row.getRemark());
        return college;
    }

    private ArtSchoolMajor toMajorEntity(ArtSchoolMajorSheetExcel row, Long schoolId, Long collegeId) {
        ArtSchoolMajor major = new ArtSchoolMajor();
        major.setSchoolId(schoolId);
        major.setCollegeId(collegeId);
        major.setMajorCode(row.getMajorCode());
        major.setMajorName(row.getMajorName());
        major.setEducationLevel(row.getEducationLevel());
        major.setDurationYears(row.getDurationYears());
        major.setMajorCategory(row.getMajorCategory());
        major.setDegreeType(row.getDegreeType());
        major.setIntroduction(row.getIntroduction());
        major.setTags(row.getTags());
        major.setRemark(row.getRemark());
        return major;
    }

    private ArtSchoolEnrollPlan toPlanEntity(ArtSchoolEnrollPlanSheetExcel row, Long schoolId, Long majorId) {
        ArtSchoolEnrollPlan plan = new ArtSchoolEnrollPlan();
        plan.setSchoolId(schoolId);
        plan.setYear(row.getYear());
        plan.setProvince(row.getProvince());
        plan.setSubjectType(row.getSubjectType());
        plan.setMajorId(majorId);
        plan.setMajorName(row.getMajorName());
        plan.setEducationLevel(row.getEducationLevel());
        plan.setPlanNum(row.getPlanNum());
        plan.setRemark(row.getRemark());
        return plan;
    }

    private boolean sameSchool(String childCode, String childName, String schoolCode, String schoolName) {
        return Objects.equals(trimToEmpty(childCode), trimToEmpty(schoolCode))
            && Objects.equals(trimToEmpty(childName), trimToEmpty(schoolName));
    }

    private String schoolKey(String mainCode, String mainName) {
        return trimToEmpty(mainCode) + "##" + trimToEmpty(mainName);
    }

    private String trimToEmpty(String value) {
        return StringUtils.isBlank(value) ? "" : value.trim();
    }

    private boolean hasCollegeBindingInfo(ArtSchoolMajorSheetExcel row) {
        return row.getCollegeId() != null || StringUtils.isNotBlank(row.getCollegeCode()) || StringUtils.isNotBlank(row.getCollegeName());
    }

    private Long resolveCollegeId(ArtSchoolMajorSheetExcel row, Map<Long, Long> oldCollegeIdToNewCollegeId, Map<String, Long> collegeBizKeyToNewCollegeId) {
        if (row.getCollegeId() != null) {
            Long mappedById = oldCollegeIdToNewCollegeId.get(row.getCollegeId());
            if (mappedById != null) {
                return mappedById;
            }
        }
        Long mappedByBiz = collegeBizKeyToNewCollegeId.get(collegeKey(row.getCollegeCode(), row.getCollegeName()));
        if (mappedByBiz != null) {
            return mappedByBiz;
        }
        if (StringUtils.isNotBlank(row.getCollegeName())) {
            return collegeBizKeyToNewCollegeId.get(collegeKey(null, row.getCollegeName()));
        }
        return null;
    }

    private Long resolveMajorId(ArtSchoolEnrollPlanSheetExcel row, Map<Long, Long> oldMajorIdToNewMajorId, Map<String, Long> majorBizKeyToNewMajorId) {
        if (row.getMajorId() != null) {
            Long mappedById = oldMajorIdToNewMajorId.get(row.getMajorId());
            if (mappedById != null) {
                return mappedById;
            }
        }
        Long mappedByBiz = majorBizKeyToNewMajorId.get(majorKey(null, row.getMajorName(), row.getEducationLevel()));
        if (mappedByBiz != null) {
            return mappedByBiz;
        }
        return null;
    }

    private String collegeKey(String collegeCode, String collegeName) {
        return trimToEmpty(collegeCode) + "##" + trimToEmpty(collegeName);
    }

    private String majorKey(String majorCode, String majorName, String educationLevel) {
        return trimToEmpty(majorCode) + "##" + trimToEmpty(majorName) + "##" + trimToEmpty(educationLevel);
    }

    private List<ArtSchoolTemplateGuideSheetExcel> buildGuideRows() {
        List<ArtSchoolTemplateGuideSheetExcel> rows = new ArrayList<>();
        rows.add(new ArtSchoolTemplateGuideSheetExcel(SHEET_SCHOOL, "学校编码", "是", "学校唯一编码，导入冲突判断主键之一", "1001"));
        rows.add(new ArtSchoolTemplateGuideSheetExcel(SHEET_SCHOOL, "学校名称", "是", "学校全称，导入冲突判断主键之一", "测试学校01"));
        rows.add(new ArtSchoolTemplateGuideSheetExcel(SHEET_DETAIL, "学校编码/学校名称", "是", "用于关联学校主表", "1001/测试学校01"));
        rows.add(new ArtSchoolTemplateGuideSheetExcel(SHEET_CAMPUS, "校区名称", "是", "同一学校下校区名称建议唯一", "主校区"));
        rows.add(new ArtSchoolTemplateGuideSheetExcel(SHEET_COLLEGE, "学院名称", "是", "同一学校下学院名称建议唯一", "设计学院"));
        rows.add(new ArtSchoolTemplateGuideSheetExcel(SHEET_MAJOR, "学院ID/学院编码/学院名称", "是", "专业关联学院，建议优先使用学院编码+学院名称，学院ID可作为兼容字段", "D001/设计学院"));
        rows.add(new ArtSchoolTemplateGuideSheetExcel(SHEET_MAJOR, "专业名称", "是", "专业名称", "视觉传达设计"));
        rows.add(new ArtSchoolTemplateGuideSheetExcel(SHEET_ENROLL_PLAN, "年份/招生省份/专业名称/计划数", "是", "招生计划关键字段", "2026/河南/视觉传达设计/120"));
        rows.add(new ArtSchoolTemplateGuideSheetExcel(SHEET_GUIDE, "replaceMainCodes", "否", "导入执行接口参数：仅替换指定学校编码", "[\"1001\",\"1002\"]"));
        return rows;
    }

    private static class SchoolWorkbookData {
        private List<ArtSchoolSheetExcel> schools = Collections.emptyList();
        private List<ArtSchoolDetailSheetExcel> details = Collections.emptyList();
        private List<ArtSchoolCampusSheetExcel> campuses = Collections.emptyList();
        private List<ArtSchoolCollegeSheetExcel> colleges = Collections.emptyList();
        private List<ArtSchoolMajorSheetExcel> majors = Collections.emptyList();
        private List<ArtSchoolEnrollPlanSheetExcel> plans = Collections.emptyList();

        public List<ArtSchoolSheetExcel> getSchools() {
            return schools;
        }

        public void setSchools(List<ArtSchoolSheetExcel> schools) {
            this.schools = Optional.ofNullable(schools).orElse(Collections.emptyList());
        }

        public List<ArtSchoolDetailSheetExcel> getDetails() {
            return details;
        }

        public void setDetails(List<ArtSchoolDetailSheetExcel> details) {
            this.details = Optional.ofNullable(details).orElse(Collections.emptyList());
        }

        public List<ArtSchoolCampusSheetExcel> getCampuses() {
            return campuses;
        }

        public void setCampuses(List<ArtSchoolCampusSheetExcel> campuses) {
            this.campuses = Optional.ofNullable(campuses).orElse(Collections.emptyList());
        }

        public List<ArtSchoolCollegeSheetExcel> getColleges() {
            return colleges;
        }

        public void setColleges(List<ArtSchoolCollegeSheetExcel> colleges) {
            this.colleges = Optional.ofNullable(colleges).orElse(Collections.emptyList());
        }

        public List<ArtSchoolMajorSheetExcel> getMajors() {
            return majors;
        }

        public void setMajors(List<ArtSchoolMajorSheetExcel> majors) {
            this.majors = Optional.ofNullable(majors).orElse(Collections.emptyList());
        }

        public List<ArtSchoolEnrollPlanSheetExcel> getPlans() {
            return plans;
        }

        public void setPlans(List<ArtSchoolEnrollPlanSheetExcel> plans) {
            this.plans = Optional.ofNullable(plans).orElse(Collections.emptyList());
        }
    }

    private static class WorkbookValidateResult {
        private List<ArtSchoolImportDetailVo> invalidDetails = Collections.emptyList();
        private Set<String> invalidSchoolKeys = Collections.emptySet();

        public List<ArtSchoolImportDetailVo> getInvalidDetails() {
            return invalidDetails;
        }

        public void setInvalidDetails(List<ArtSchoolImportDetailVo> invalidDetails) {
            this.invalidDetails = Optional.ofNullable(invalidDetails).orElse(Collections.emptyList());
        }

        public Set<String> getInvalidSchoolKeys() {
            return invalidSchoolKeys;
        }

        public void setInvalidSchoolKeys(Set<String> invalidSchoolKeys) {
            this.invalidSchoolKeys = Optional.ofNullable(invalidSchoolKeys).orElse(Collections.emptySet());
        }
    }

    /**
     * 校验并批量删除学校基础信息主表信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
    }
}
