package org.dromara.art.controller;

import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.databind.JsonNode;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.art.domain.bo.*;
import org.dromara.art.domain.vo.ArtSchoolImportDetailVo;
import org.dromara.art.domain.vo.ArtSchoolJsonImportPreviewVo;
import org.dromara.art.domain.vo.ArtSchoolVo;
import org.dromara.art.service.IArtSchoolDetailService;
import org.dromara.art.service.IArtSchoolService;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.web.core.BaseController;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/art/test")
@Slf4j
public class ArtTestController extends BaseController {

  private final IArtSchoolService artSchoolService;
  private final IArtSchoolDetailService artSchoolDetailService;

  @Value("${art.import.schoolJsonDir:}")
  private String schoolJsonDir;

  @GetMapping("/importSchoolByJson")
  public R<ArtSchoolJsonImportPreviewVo> importSchoolByJson(
    @RequestParam(defaultValue = "20") Integer previewSize,
    @RequestParam(defaultValue = "true") Boolean async,
    @RequestParam(defaultValue = "8") Integer threadCount
  ) {
    int safePreviewSize =
      previewSize == null ? 20 : Math.min(Math.max(previewSize, 1), 200);
    int safeThreadCount = resolveThreadCount(threadCount);
    Path sourceDir;
    if (StringUtils.isNotBlank(schoolJsonDir)) {
      sourceDir = Path.of(schoolJsonDir.trim());
    } else {
      sourceDir = Path.of(System.getProperty("user.dir"), "get_univ");
    }
    if (!Files.exists(sourceDir) || !Files.isDirectory(sourceDir)) {
      return R.fail("目录不存在: " + sourceDir);
    }

    List<Path> fileList;
    try {
      fileList = listJsonFiles(sourceDir);
    } catch (RuntimeException e) {
      return R.fail(e.getMessage());
    }
    ArtSchoolJsonImportPreviewVo previewVo = new ArtSchoolJsonImportPreviewVo();
    previewVo.setSourceDir(sourceDir.toString());
    previewVo.setTotalFileCount(fileList.size());

    List<ArtSchoolImportDetailVo> details = new ArrayList<>();
    List<ArtSchoolSubmitBo> submitPreviewList = new ArrayList<>();
    List<ArtSchoolSubmitBo> submitAllList = new ArrayList<>();
    Set<Integer> fileUnivIdSet = new LinkedHashSet<>();
    Set<String> fileMainCodeSet = new LinkedHashSet<>();

    int readyCount = 0;
    int duplicateInDbCount = 0;
    int duplicateInFileCount = 0;
    int invalidCount = 0;
    int readFailCount = 0;

    for (Path file : fileList) {
      String fileName = file.getFileName().toString();
      JsonNode root;
      try {
        root = JsonUtils.getObjectMapper().readTree(file.toFile());
      } catch (Exception e) {
        readFailCount++;
        appendDetail(
          details,
          safePreviewSize,
          null,
          null,
          "FAILED",
          fileName + " 读取失败: " + e.getMessage()
        );
        continue;
      }

      List<JsonNode> schoolNodes = extractSchoolNodes(root);
      if (CollUtil.isEmpty(schoolNodes)) {
        invalidCount++;
        appendDetail(
          details,
          safePreviewSize,
          null,
          null,
          "INVALID",
          fileName + " 缺少有效学校对象"
        );
        continue;
      }

      for (int i = 0; i < schoolNodes.size(); i++) {
        JsonNode schoolNode = schoolNodes.get(i);
        String sourceTag =
          schoolNodes.size() == 1 ? fileName : (fileName + "#" + i);

        Integer univId = asInteger(schoolNode.get("univId"));
        String mainName = asText(schoolNode.get("univName"));
        System.out.println("处理文件：" + fileName);
        if (univId == null || StringUtils.isBlank(mainName)) {
          invalidCount++;
          appendDetail(
            details,
            safePreviewSize,
            null,
            mainName,
            "INVALID",
            sourceTag + " 缺少必填字段: univId/univName"
          );
          continue;
        }

        String mainCode = String.valueOf(univId);
        if (!fileUnivIdSet.add(univId) || !fileMainCodeSet.add(mainCode)) {
          duplicateInFileCount++;
          appendDetail(
            details,
            safePreviewSize,
            mainCode,
            mainName,
            "DUPLICATE_FILE",
            sourceTag + " 文件内重复（univId/mainCode）"
          );
          continue;
        }

        /*if (existsInDatabase(mainCode, univId)) {
                    duplicateInDbCount++;
                    appendDetail(details, safePreviewSize, mainCode, mainName, "DUPLICATE_DB", sourceTag + " 数据库已存在（mainCode 或 univId）");
                    continue;
                }*/

        ArtSchoolSubmitBo submitBo = buildSubmitBo(
          schoolNode,
          sourceTag,
          univId,
          mainCode,
          mainName
        );
        readyCount++;
        appendDetail(
          details,
          safePreviewSize,
          mainCode,
          mainName,
          "READY",
          sourceTag + " 已完成结构映射，待入库"
        );

        if (submitPreviewList.size() < safePreviewSize) {
          submitPreviewList.add(toPreviewSubmitBo(submitBo));
        }

        submitAllList.add(submitBo);
      }
    }

    previewVo.setReadyCount(readyCount);
    previewVo.setDuplicateInDbCount(duplicateInDbCount);
    previewVo.setDuplicateInFileCount(duplicateInFileCount);
    previewVo.setInvalidCount(invalidCount);
    previewVo.setReadFailCount(readFailCount);
    previewVo.setDetails(details);
    previewVo.setSubmitPreviewList(submitPreviewList);
    previewVo.setSubmitTotalCount(submitAllList.size());
    previewVo.setThreadCount(safeThreadCount);
    previewVo.setAsync(Boolean.TRUE.equals(async));
    if (CollUtil.isEmpty(submitAllList)) {
      previewVo.setServiceCallHint("未发现可入库数据");
      return R.ok(previewVo);
    }

    if (Boolean.TRUE.equals(async)) {
      String taskId = UUID.randomUUID().toString();
      previewVo.setAsyncTaskId(taskId);
      previewVo.setServiceCallHint(
        "异步任务已提交: taskId=" +
        taskId +
        "，预计入库数量=" +
        submitAllList.size()
      );
      CompletableFuture.runAsync(() -> {
        ImportStats stats = importWithThreadPool(taskId, submitAllList, safeThreadCount);
        log.info(
          "学校JSON导入任务完成 taskId={}, total={}, success={}, fail={}",
          taskId,
          stats.total,
          stats.success,
          stats.fail
        );
      });
    } else {
      ImportStats stats = importWithThreadPool("sync", submitAllList, safeThreadCount);
      previewVo.setImportSuccessCount(stats.success);
      previewVo.setImportFailCount(stats.fail);
      previewVo.setServiceCallHint(
        "入库完成: total=" +
        stats.total +
        ", success=" +
        stats.success +
        ", fail=" +
        stats.fail
      );
    }
    return R.ok(previewVo);
  }

  private int resolveThreadCount(Integer threadCount) {
    int cpu = Runtime.getRuntime().availableProcessors();
    int defaultThreads = Math.min(8, Math.max(2, cpu));
    int maxThreads = Math.max(2, cpu * 2);
    if (threadCount == null) {
      return defaultThreads;
    }
    return Math.min(Math.max(threadCount, 1), maxThreads);
  }

  private ImportStats importWithThreadPool(
    String taskId,
    List<ArtSchoolSubmitBo> submitAllList,
    int threadCount
  ) {
    ExecutorService executor = Executors.newFixedThreadPool(threadCount);
    List<CompletableFuture<Boolean>> futures = new ArrayList<>(submitAllList.size());
    for (ArtSchoolSubmitBo submitBo : submitAllList) {
      futures.add(
        CompletableFuture.supplyAsync(() -> importSingle(taskId, submitBo), executor)
      );
    }
    int success = 0;
    int fail = 0;
    for (CompletableFuture<Boolean> future : futures) {
      try {
        if (Boolean.TRUE.equals(future.join())) {
          success++;
        } else {
          fail++;
        }
      } catch (Exception e) {
        fail++;
        log.warn("学校JSON导入任务失败 taskId={}, error={}", taskId, e.getMessage(), e);
      }
    }
    executor.shutdown();
    return new ImportStats(submitAllList.size(), success, fail);
  }

  private boolean importSingle(String taskId, ArtSchoolSubmitBo artSchoolSubmitBo) {
    try {
      ArtSchoolBo artSchoolBo = new ArtSchoolBo();
      artSchoolBo.setMainName(artSchoolSubmitBo.getSchool().getMainName());
      List<ArtSchoolVo> artSchoolVos = artSchoolService.queryList(artSchoolBo);
      if (!artSchoolVos.isEmpty()) {
        artSchoolSubmitBo
          .getSchool()
          .setSchoolId(artSchoolVos.get(0).getSchoolId());
        artSchoolService.updateWithDetailByBo(artSchoolSubmitBo);
      } else {
        artSchoolService.insertWithDetailByBo(artSchoolSubmitBo);
      }
      return true;
    } catch (Exception e) {
      log.warn(
        "学校JSON导入任务单条失败 taskId={}, name={}, error={}",
        taskId,
        artSchoolSubmitBo.getSchool().getMainName(),
        e.getMessage(),
        e
      );
      return false;
    }
  }

  private static class ImportStats {
    private final int total;
    private final int success;
    private final int fail;

    private ImportStats(int total, int success, int fail) {
      this.total = total;
      this.success = success;
      this.fail = fail;
    }
  }

  private List<Path> listJsonFiles(Path sourceDir) {
    try (Stream<Path> stream = Files.list(sourceDir)) {
      return stream
        .filter(Files::isRegularFile)
        .filter(path -> path.getFileName().toString().endsWith(".json"))
        .sorted()
        .toList();
    } catch (Exception e) {
      throw new RuntimeException("读取目录失败: " + sourceDir, e);
    }
  }

  private List<JsonNode> extractSchoolNodes(JsonNode root) {
    if (root == null || root.isNull()) {
      return List.of();
    }
    if (root.isArray()) {
      List<JsonNode> nodes = new ArrayList<>();
      for (JsonNode item : root) {
        if (item != null && item.isObject()) {
          nodes.add(item);
        }
      }
      return nodes;
    }
    if (root.isObject()) {
      return List.of(root);
    }
    return List.of();
  }

  private boolean existsInDatabase(String mainCode, Integer univId) {
    ArtSchoolBo schoolBo = new ArtSchoolBo();
    schoolBo.setMainCode(mainCode);
    if (CollUtil.isNotEmpty(artSchoolService.queryList(schoolBo))) {
      return true;
    }
    ArtSchoolDetailBo detailBo = new ArtSchoolDetailBo();
    detailBo.setUnivId(univId);
    return CollUtil.isNotEmpty(artSchoolDetailService.queryList(detailBo));
  }

  private ArtSchoolSubmitBo buildSubmitBo(
    JsonNode node,
    String sourceTag,
    Integer univId,
    String mainCode,
    String mainName
  ) {
    ArtSchoolBo school = new ArtSchoolBo();
    school.setMainCode(mainCode);
    school.setMainName(mainName);
    school.setProvince(asText(node.get("provId"))); // provName
    school.setCity(asText(node.get("city")));
    school.setDistrict(null);
    school.setUniversityType(asText(node.get("univTypeName")));
    school.setEducationLevel(resolveEducationLevel(node));
    //school.setSchoolNature(resolveSchoolNature(asInteger(node.get("isPublic"))));
    school.setSupervisorDept(asText(node.get("competentDepart")));
    school.setRemark("source=" + sourceTag);

    ArtSchoolDetailBo detail = new ArtSchoolDetailBo();
    detail.setIntroduction(asText(node.get("univDesc"))); // 院校介绍
    detail.setSchoolIcon(asText(node.get("logo"))); // 校徽
    detail.setBackGround(asText(node.get("backGround"))); // 背景图
    detail.setAddress(asText(node.get("univAddress"))); // 详细地址
    detail.setContact(asText(node.get("contact"))); // 联系方式
    detail.setEmail(null); // 邮箱
    detail.setWebsite(asText(node.get("url"))); // 官方网址
    detail.setPostcode(null);
    detail.setEstablishYear(asInteger(node.get("establishYear")));
    detail.setCampusAreaMu(null);
    detail.setLibraryCollection(null);
    BigDecimal femaleRatio = asDecimal(node.get("girlProportion"));
    detail.setFemaleRatio(femaleRatio);
    detail.setMaleRatio(resolveMaleRatio(femaleRatio));

    detail.setIs985(normalizeFlag(asInteger(node.get("is985"))));
    detail.setIs211(normalizeFlag(asInteger(node.get("is211"))));
    detail.setIsDoubleFirstClass(
      normalizeFlag(asInteger(node.get("isFirstClass")))
    );
    detail.setIsKeyUniversity(
      normalizeFlag(asInteger(node.get("isFirstLevel")))
    );
    detail.setIsPublic(normalizeFlag(asInteger(node.get("isPublic"))));

    detail.setMasterProportionRate(asDecimal(node.get("masterProportion"))); // 考研率
    detail.setAbroadProportionRate(asDecimal(node.get("abroadProportion"))); // 出国率

    detail.setHasRegular(normalizeFlag(asInteger(node.get("hasRegular")))); // 是否有本科
    detail.setHasJunior(normalizeFlag(asInteger(node.get("hasJunior")))); // 是否有专科
    detail.setHasMaster(normalizeFlag(asInteger(node.get("hasMaster")))); // 是否有研究生

    detail.setIsDoubleHighPlan(
      normalizeFlag(asInteger(node.get("isDoubleHighPlan")))
    ); // 是否双高计划（专科用）
    detail.setIsStrongPlan(normalizeFlag(asInteger(node.get("isStrongPlan")))); // 是否强基计划
    detail.setTwsdlRank(normalizeFlag(asInteger(node.get("twsdl"))));
    detail.setXyhRank(normalizeFlag(asInteger(node.get("xyh"))));
    detail.setWslRank(normalizeFlag(asInteger(node.get("wsl")))); // 武书连排名
    detail.setUsdaluRank(normalizeFlag(asInteger(node.get("usdalu")))); // 美国大学排名相关
    detail.setQsdaluRank(normalizeFlag(asInteger(node.get("qsdalu")))); // QS世界大学排名

    detail.setTags(buildTags(node));
    detail.setStudentCount(null);
    detail.setTeacherCount(null);
    detail.setMasterPoint(resolveMasterPoint(node));
    detail.setDoctorPoint(resolveDoctorPoint(node));
    detail.setKeyMajorCount(resolveKeyMajorCount(node));
    detail.setEmploymentRate(resolveEmploymentRate(node));
    detail.setSatisfactionRate(resolveSatisfactionRate(node));
    // 评分/满意度相关原始数据与明细保留，便于后续展示或二次计算
    detail.setCombinedScore(asDecimal(node.get("combinedScore")));
    detail.setOverallRank(asInteger(node.get("rank")));
    detail.setEnvSatisfaction(asDecimal(node.get("envSatisfaction")));
    detail.setEnvVote(asInteger(node.get("envVote")));
    detail.setLiveSatisfaction(asDecimal(node.get("liveSatisfaction")));
    detail.setLiveVote(asInteger(node.get("liveVote")));
    detail.setCombinedSatisfaction(asDecimal(node.get("combinedSatisfaction")));
    detail.setCombinedVote(asInteger(node.get("combinedVote")));
    detail.setSatisfactionJson(toJsonString(node.get("satisfactionJson")));
    detail.setTeachers(asText(node.get("teachers")));
    detail.setScholarship(asText(node.get("scholarship")));
    detail.setScholarshipJson(toJsonString(node.get("scholarshipArray")));
    detail.setGrantDesc(asText(node.get("grant")));
    detail.setCanteen(asText(node.get("canteen")));
    detail.setDormitory(asText(node.get("dormitory")));
    detail.setMasterExplain(asText(node.get("masterExplain")));
    detail.setDoctorExplain(asText(node.get("doctorExplain")));
    // 结构化内容以 JSON 文本落库，避免一次性拆表
    detail.setSpecialMajorJson(toJsonString(node.get("specialMajor")));
    detail.setEmploymentReportJson(toJsonString(node.get("employmentReport")));
    detail.setPhotoJson(toJsonString(node.get("photoJson")));
    detail.setAccommodationJson(toJsonString(node.get("accommodationArray")));
    detail.setSubjectReviewsJson(toJsonString(node.get("subjectReviews")));
    detail.setResearchJson(toJsonString(node.get("researchJson")));
    detail.setUnivMajorsJson(toJsonString(node.get("univMajors")));
    detail.setUnivPostgraduateJson(
      toJsonString(node.get("univPostgraduateList"))
    );
    detail.setUnivId(univId);
    //detail.setRemark("source=" + sourceTag);

    ArtSchoolCollegeBo schoolCollegeBo = null;
    List<ArtSchoolCollegeBo> schoolCollegeBoList = null;

    ArtSchoolMajorBo artSchoolMajorBo = null;
    List<ArtSchoolMajorBo> schoolMajorBoList = null;
    // 学院信息
      if (node.has("college")) {
        JsonNode collegeNode = node.get("college");
        schoolCollegeBoList = new ArrayList<>();
          if (!collegeNode.isEmpty()) {
            for (JsonNode college : collegeNode) {
              schoolCollegeBo = new ArtSchoolCollegeBo();
                if (StringUtils.isBlank(asText(college.get("collegeName")))) {
                    continue;
                }
              schoolCollegeBo.setCollegeName(asText(college.get("collegeName")));
              schoolCollegeBo.setCollege51sdxId(asInteger(college.get("collegeId")));

              schoolMajorBoList = new ArrayList<>();
              // 学院专业列表
              if (college.has("majorList")) {
                for (JsonNode major : college.get("majorList")) {
                  artSchoolMajorBo = new ArtSchoolMajorBo();
                  if (StringUtils.isBlank(asText(major.get("majorName")))) {
                    continue;
                  }
                  artSchoolMajorBo.setEducationLevel(
                          "(本)".equals(asText(major.get("majorLevel"))) ? "1" : "0"
                  );
                  artSchoolMajorBo.setMajorName(asText(major.get("majorName")));
                  artSchoolMajorBo.setMajor51sdxId(asText(major.get("majorId")));
                  schoolMajorBoList.add(artSchoolMajorBo);
                }
                schoolCollegeBo.setSchoolMajorBoList(schoolMajorBoList);
              }
              schoolCollegeBoList.add(schoolCollegeBo);
            }
          }
      }


    // 其余结构化内容已映射到详情 JSON 字段

    ArtSchoolSubmitBo submitBo = new ArtSchoolSubmitBo();
    submitBo.setSchool(school);
    submitBo.setDetail(detail);
    submitBo.setArtSchoolCollegeBoList(schoolCollegeBoList);
    submitBo.setEnrollCodes(List.of());
    submitBo.setSchoolTags(List.of());
    return submitBo;
  }

  private ArtSchoolSubmitBo toPreviewSubmitBo(ArtSchoolSubmitBo submitBo) {
    ArtSchoolSubmitBo preview = new ArtSchoolSubmitBo();
    preview.setSchool(submitBo.getSchool());
    ArtSchoolDetailBo detail = submitBo.getDetail();
    if (detail == null) {
      preview.setDetail(null);
    } else {
      /*ArtSchoolDetailBo detailPreview = new ArtSchoolDetailBo();
            detailPreview.setUnivId(detail.getUnivId());
            detailPreview.setSchoolIcon(detail.getSchoolIcon());
            detailPreview.setAddress(detail.getAddress());
            detailPreview.setContact(detail.getContact());
            detailPreview.setWebsite(detail.getWebsite());
            detailPreview.setEstablishYear(detail.getEstablishYear());
            detailPreview.setFemaleRatio(detail.getFemaleRatio());
            detailPreview.setMaleRatio(detail.getMaleRatio());
            detailPreview.setIs985(detail.getIs985());
            detailPreview.setIs211(detail.getIs211());
            detailPreview.setIsDoubleFirstClass(detail.getIsDoubleFirstClass());
            detailPreview.setIsKeyUniversity(detail.getIsKeyUniversity());
            detailPreview.setMasterPoint(detail.getMasterPoint());
            detailPreview.setDoctorPoint(detail.getDoctorPoint());
            detailPreview.setKeyMajorCount(detail.getKeyMajorCount());
            detailPreview.setEmploymentRate(detail.getEmploymentRate());
            detailPreview.setSatisfactionRate(detail.getSatisfactionRate());
            detailPreview.setTags(detail.getTags());
            detailPreview.setRemark(detail.getRemark());*/
      preview.setDetail(submitBo.getDetail());
    }
    preview.setArtSchoolCollegeBoList(submitBo.getArtSchoolCollegeBoList());
    preview.setEnrollCodes(submitBo.getEnrollCodes());
    preview.setSchoolTags(submitBo.getSchoolTags());
    return preview;
  }

  private String[] buildTags(JsonNode node) {
    Set<String> tags = new LinkedHashSet<>();
    if (isPositive(node.get("is985"))) {
      tags.add("985");
    }
    if (isPositive(node.get("is211"))) {
      tags.add("211");
    }
    if (isPositive(node.get("isFirstClass"))) {
      tags.add("双一流");
    }
    String typeName = asText(node.get("univTypeName"));
    if (StringUtils.isNotBlank(typeName)) {
      tags.add(typeName);
    }
    return tags.isEmpty() ? null : tags.toArray(String[]::new);
  }

  private Integer resolveMasterPoint(JsonNode node) {
    Integer mastersDegree = asInteger(node.get("mastersDegree"));
    if (mastersDegree != null) {
      return mastersDegree;
    }
    Integer shuoshidian = asInteger(node.get("shuoshidian"));
    if (shuoshidian != null) {
      return shuoshidian;
    }
    return asInteger(node.path("researchJson").path("base").get("硕士点数量"));
  }

  private Integer resolveDoctorPoint(JsonNode node) {
    Integer doctoralProgram = asInteger(node.get("doctoralProgram"));
    if (doctoralProgram != null) {
      return doctoralProgram;
    }
    Integer boshidian = asInteger(node.get("boshidian"));
    if (boshidian != null) {
      return boshidian;
    }
    return asInteger(node.path("researchJson").path("base").get("博士点数量"));
  }

  private Integer resolveKeyMajorCount(JsonNode node) {
    Integer specialMajorCount = asInteger(node.get("specialMajorCount"));
    if (specialMajorCount != null) {
      return specialMajorCount;
    }
    Integer guojiatese = asInteger(node.get("guojiatese"));
    if (guojiatese != null) {
      return guojiatese;
    }
    return asInteger(
      node.path("researchJson").path("base").get("重点专业数量")
    );
  }

  private BigDecimal resolveEmploymentRate(JsonNode node) {
    BigDecimal employmentRate = asDecimal(node.get("jiuyelu"));
    return employmentRate == null
      ? null
      : employmentRate.setScale(2, RoundingMode.HALF_UP);
  }

  private BigDecimal resolveSatisfactionRate(JsonNode node) {
    BigDecimal score = asDecimal(node.get("combinedSatisfaction"));
    if (score == null) {
      return null;
    }
    if (score.compareTo(new BigDecimal("5")) <= 0) {
      return score
        .multiply(new BigDecimal("20"))
        .setScale(2, RoundingMode.HALF_UP);
    }
    return score.setScale(2, RoundingMode.HALF_UP);
  }

  private BigDecimal resolveMaleRatio(BigDecimal femaleRatio) {
    if (femaleRatio == null) {
      return null;
    }
    return new BigDecimal("100")
      .subtract(femaleRatio)
      .setScale(2, RoundingMode.HALF_UP);
  }

  private String resolveEducationLevel(JsonNode node) {
    Integer hasRegular = asInteger(node.get("hasRegular"));
    Integer hasJunior = asInteger(node.get("hasJunior"));
    boolean regular = hasRegular != null && hasRegular == 1;
    boolean junior = hasJunior != null && hasJunior == 1;
    if (regular && junior) {
      return "本科/专科";
    }
    if (regular) {
      return "本科";
    }
    if (junior) {
      return "专科";
    }
    return null;
  }

  private String resolveSchoolNature(Integer isPublic) {
    if (isPublic == null) {
      return null;
    }
    if (isPublic == 1) {
      return "公办";
    }
    if (isPublic == 2) {
      return "民办";
    }
    return null;
  }

  private boolean isPositive(JsonNode node) {
    Integer value = asInteger(node);
    return value != null && value > 0;
  }

  private Integer normalizeFlag(Integer value) {
    if (value == null) {
      return 0;
    }
    return value > 0 ? 1 : 0;
  }

  private String asText(JsonNode node) {
    if (node == null || node.isNull()) {
      return null;
    }
    String text = node.asText();
    if (StringUtils.isBlank(text)) {
      return null;
    }
    return text.trim();
  }

  private Integer asInteger(JsonNode node) {
    if (node == null || node.isNull()) {
      return null;
    }
    if (node.isNumber()) {
      return node.intValue();
    }
    String text = asText(node);
    if (StringUtils.isBlank(text)) {
      return null;
    }
    try {
      return Integer.valueOf(text.replace("%", "").trim());
    } catch (Exception e) {
      return null;
    }
  }

  private BigDecimal asDecimal(JsonNode node) {
    if (node == null || node.isNull()) {
      return null;
    }
    String text = node.isNumber()
      ? node.numberValue().toString()
      : asText(node);
    if (StringUtils.isBlank(text)) {
      return null;
    }
    try {
      return new BigDecimal(text.replace("%", "").trim());
    } catch (Exception e) {
      return null;
    }
  }

  private String toJsonString(JsonNode node) {
    if (node == null || node.isNull()) {
      return null;
    }
    // 统一走 ObjectMapper 序列化，保持 JSON 原始结构
    return JsonUtils.toJsonString(node);
  }

  private void appendDetail(
    List<ArtSchoolImportDetailVo> details,
    int maxSize,
    String mainCode,
    String mainName,
    String status,
    String message
  ) {
    if (details.size() >= maxSize) {
      return;
    }
    ArtSchoolImportDetailVo detailVo = new ArtSchoolImportDetailVo();
    detailVo.setMainCode(mainCode);
    detailVo.setMainName(mainName);
    detailVo.setStatus(status);
    detailVo.setMessage(message);
    details.add(detailVo);
  }
}
