package com.rj.service.impl;

import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rj.entity.LbAssessmentApply;
import com.rj.mapper.LbAssessmentApplyMapper;
import com.rj.service.DingTalkRobotService;
import com.rj.service.ILbAssessmentApplyService;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;

import java.awt.Color;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@Slf4j
@Service
public class LbAssessmentApplyServiceImpl
        extends ServiceImpl<LbAssessmentApplyMapper, LbAssessmentApply>
        implements ILbAssessmentApplyService {

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private DingTalkRobotService dingTalkRobotService;

    @Value("${langchain4j.open-ai.chat-model.model-name:qwen-plus}")
    private String dashScopeChatModel;

    private static final String ASSESSMENT_PARSE_SYSTEM_PROMPT = """
            你是信息抽取助手。用户会提供一段与「申请评估」相关的自然语言描述。
            请只输出一个 JSON 对象，不要 markdown 代码块，不要解释性文字。
            字段均为可选；无法从原文推断的字段请省略或设为 null。
            字段名与含义（JSON 键名必须完全一致，与 Java 驼峰一致）：
            applicantName 申请人姓名；
            applicantPhone 申请人电话；
            applicantAge 年龄（整数）；
            workExperience 从业经历；
            colleagueName 同事姓名；
            colleaguePhone 同事电话；
            teamLeaderName 团队长姓名；
            teamBigLeaderName 全收益团队长姓名；
            assessmentTeacherName 评估老师姓名；
            moderatorName 主持人姓名；
            meetingNumber 会议号；
            applicationDatetime 申请日期时间，优先 ISO-8601（如 2026-05-15T14:30:00），也可 yyyy-MM-dd HH:mm:ss；若仅有日期可设为当天 00:00:00。
            不要输出 id、tenantId、createdAt、updatedAt、notificationText。
            """;

    @Override
    public Map<String, Object> parseFromAssessmentTextByLlm(String assessmentText, String tenantId) {
        Map<String, Object> result = new HashMap<>();
        try {
            if (assessmentText == null || assessmentText.trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "申请评估文本不能为空");
                return result;
            }
            if (tenantId == null || tenantId.trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "tenantId不能为空");
                return result;
            }

            String apiKey = System.getenv("DASHSCOPE_API_KEY");
            if (apiKey == null || apiKey.isEmpty()) {
                result.put("success", false);
                result.put("message", "未配置环境变量 DASHSCOPE_API_KEY，无法调用大模型");
                return result;
            }

            Generation gen = new Generation();
            Message systemMsg = Message.builder()
                    .role(Role.SYSTEM.getValue())
                    .content(ASSESSMENT_PARSE_SYSTEM_PROMPT)
                    .build();
            Message userMsg = Message.builder()
                    .role(Role.USER.getValue())
                    .content("申请评估信息如下：\n" + assessmentText.trim())
                    .build();

            GenerationParam param = GenerationParam.builder()
                    .apiKey(apiKey)
                    .model(dashScopeChatModel)
                    .messages(Arrays.asList(systemMsg, userMsg))
                    .resultFormat(GenerationParam.ResultFormat.MESSAGE)
                    .build();

            GenerationResult call = gen.call(param);
            String raw = call.getOutput().getChoices().get(0).getMessage().getContent();
            if (raw == null || raw.trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "大模型返回内容为空");
                return result;
            }

            String json = normalizeJson(raw);
            LbAssessmentApply entity = objectMapper.readValue(json, LbAssessmentApply.class);

            entity.setTenantId(tenantId.trim());
            entity.setId(null);
            entity.setCreatedAt(null);
            entity.setUpdatedAt(null);

            result.put("success", true);
            result.put("message", "解析成功");
            result.put("data", entity);
            return result;
        } catch (NoApiKeyException e) {
            log.error("DashScope API Key 异常", e);
            result.put("success", false);
            result.put("message", "API密钥异常：" + e.getMessage());
            return result;
        } catch (InputRequiredException e) {
            log.error("DashScope 入参异常", e);
            result.put("success", false);
            result.put("message", "调用大模型入参错误：" + e.getMessage());
            return result;
        } catch (Exception e) {
            log.error("申请评估信息大模型解析失败", e);
            result.put("success", false);
            result.put("message", "解析失败：" + e.getMessage());
            return result;
        }
    }

    private static String normalizeJson(String content) {
        if (content == null) {
            return "";
        }
        String trimmed = content.trim();
        if (trimmed.startsWith("```")) {
            int firstLineBreak = trimmed.indexOf('\n');
            int lastFence = trimmed.lastIndexOf("```");
            if (firstLineBreak >= 0 && lastFence > firstLineBreak) {
                trimmed = trimmed.substring(firstLineBreak + 1, lastFence).trim();
            }
        }
        return trimmed;
    }

    @Override
    public Map<String, Object> add(LbAssessmentApply entity) {
        Map<String, Object> result = new HashMap<>();
        try {
            if (entity.getTenantId() == null) {
                result.put("success", false);
                result.put("message", "tenantId不能为空");
                return result;
            }
            if (entity.getApplicantName() == null || entity.getApplicantName().trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "applicantName不能为空");
                return result;
            }
            if (entity.getApplicantPhone() == null || entity.getApplicantPhone().trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "applicantPhone不能为空");
                return result;
            }

            if (entity.getId() == null || entity.getId().trim().isEmpty()) {
                entity.setId(UUID.randomUUID().toString());
            }
            LocalDateTime now = LocalDateTime.now();
            if (entity.getCreatedAt() == null) {
                entity.setCreatedAt(now);
            }
            entity.setUpdatedAt(now);
            refreshNotificationText(entity);

            boolean ok = this.save(entity);
            result.put("success", ok);
            result.put("message", ok ? "新增成功" : "新增失败");
            if (ok) {
                result.put("data", entity);
            }
            return result;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "新增异常：" + e.getMessage());
            return result;
        }
    }

    @Override
    public Map<String, Object> update(LbAssessmentApply entity) {
        Map<String, Object> result = new HashMap<>();
        try {
            if (entity.getId() == null || entity.getId().trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "id不能为空");
                return result;
            }
            entity.setUpdatedAt(LocalDateTime.now());
            boolean ok = this.updateById(entity);
            if (ok) {
                persistNotificationText(entity.getId());
            }
            result.put("success", ok);
            result.put("message", ok ? "编辑成功" : "编辑失败");
            if (ok) {
                result.put("data", this.getById(entity.getId()));
            }
            return result;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "编辑异常：" + e.getMessage());
            return result;
        }
    }

    @Override
    public Map<String, Object> deleteById(String id) {
        Map<String, Object> result = new HashMap<>();
        try {
            boolean ok = this.removeById(id);
            result.put("success", ok);
            result.put("message", ok ? "删除成功" : "删除失败");
            return result;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "删除异常：" + e.getMessage());
            return result;
        }
    }

    @Override
    public Map<String, Object> pageQuery(Integer current,
                                         Integer size,
                                         String tenantId,
                                         String applicantName,
                                         String applicantPhone,
                                         String colleagueName,
                                         String teamLeaderName,
                                         String teamBigLeaderName,
                                         String assessmentTeacherName,
                                         String moderatorName,
                                         String meetingNumber) {
        Map<String, Object> result = new HashMap<>();
        try {
            if (current == null || current < 1) {
                current = 1;
            }
            if (size == null || size < 1) {
                size = 10;
            }

            LambdaQueryWrapper<LbAssessmentApply> queryWrapper = buildQueryWrapper(
                    tenantId, applicantName, applicantPhone, colleagueName, teamLeaderName,
                    teamBigLeaderName, assessmentTeacherName, moderatorName, meetingNumber
            );

            Page<LbAssessmentApply> page = this.page(new Page<>(current, size), queryWrapper);
            result.put("success", true);
            result.put("message", "查询成功");
            result.put("data", page.getRecords());
            result.put("total", page.getTotal());
            result.put("current", page.getCurrent());
            result.put("size", page.getSize());
            result.put("pages", page.getPages());
            return result;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "查询异常：" + e.getMessage());
            log.error( "查询异常", e);
            return result;
        }
    }

    private static final DateTimeFormatter NOTIFICATION_DATE_TIME_FORMATTER =
            DateTimeFormatter.ofPattern("M月d日  HH:mm");
    private static final DateTimeFormatter MODERATOR_DATE_TIME_FORMATTER =
            DateTimeFormatter.ofPattern("M月d日 H:mm");
    private static final long MODERATOR_CONNECT_MINUTES_BEFORE = 20L;

    @Override
    public Map<String, Object> buildAssessmentNotificationText(String tenantId, String id) {
        Map<String, Object> result = new HashMap<>();
        try {
            if (tenantId == null || tenantId.trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "tenantId不能为空");
                return result;
            }
            if (id == null || id.trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "id不能为空");
                return result;
            }

            LbAssessmentApply record = getByTenantIdAndId(tenantId, id);
            if (record == null) {
                result.put("success", false);
                result.put("message", "未找到对应的申请评估记录");
                return result;
            }

            String text = ensureAndSaveNotificationText(record);
            result.put("success", true);
            result.put("message", "生成成功");
            result.put("data", text);
            return result;
        } catch (Exception e) {
            log.error("生成评估会议通知文案失败, tenantId={}, id={}", tenantId, id, e);
            result.put("success", false);
            result.put("message", "生成失败：" + e.getMessage());
            return result;
        }
    }

    @Override
    public Map<String, Object> sendNotificationTextToDingTalk(String tenantId, String id) {
        Map<String, Object> result = new HashMap<>();
        try {
            if (tenantId == null || tenantId.trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "tenantId不能为空");
                return result;
            }
            if (id == null || id.trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "id不能为空");
                return result;
            }

            LbAssessmentApply record = getByTenantIdAndId(tenantId, id);
            if (record == null) {
                result.put("success", false);
                result.put("message", "未找到对应的申请评估记录");
                return result;
            }

            String text = ensureAndSaveNotificationText(record);
            dingTalkRobotService.sendText(text);

            result.put("success", true);
            result.put("message", "发送成功");
            result.put("data", text);
            return result;
        } catch (IllegalArgumentException | IllegalStateException e) {
            log.warn("发送评估会议通知到钉钉失败, tenantId={}, id={}, reason={}", tenantId, id, e.getMessage());
            result.put("success", false);
            result.put("message", e.getMessage());
            return result;
        } catch (Exception e) {
            log.error("发送评估会议通知到钉钉异常, tenantId={}, id={}", tenantId, id, e);
            result.put("success", false);
            result.put("message", "发送失败：" + e.getMessage());
            return result;
        }
    }

    private LbAssessmentApply getByTenantIdAndId(String tenantId, String id) {
        LambdaQueryWrapper<LbAssessmentApply> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim())
                .eq(LbAssessmentApply::getId, id.trim());
        return this.getOne(queryWrapper);
    }

    /**
     * 返回可用的通知文案：库中已有则直接返回，否则生成并落库。
     */
    private String ensureAndSaveNotificationText(LbAssessmentApply record) {
        String text = record.getNotificationText();
        if (text != null && !text.trim().isEmpty()) {
            return text;
        }
        text = formatAssessmentNotificationText(record);
        LbAssessmentApply update = new LbAssessmentApply();
        update.setId(record.getId());
        update.setNotificationText(text);
        update.setUpdatedAt(LocalDateTime.now());
        if (!this.updateById(update)) {
            throw new IllegalStateException("通知文案保存失败");
        }
        record.setNotificationText(text);
        return text;
    }

    private void refreshNotificationText(LbAssessmentApply entity) {
        entity.setNotificationText(formatAssessmentNotificationText(entity));
    }

    private void persistNotificationText(String id) {
        LbAssessmentApply record = this.getById(id);
        if (record == null) {
            return;
        }
        LbAssessmentApply update = new LbAssessmentApply();
        update.setId(id);
        update.setNotificationText(formatAssessmentNotificationText(record));
        update.setUpdatedAt(LocalDateTime.now());
        this.updateById(update);
    }

    private static String formatAssessmentNotificationText(LbAssessmentApply record) {
        String teacherName = nullToEmpty(record.getAssessmentTeacherName());
        String applicantName = nullToEmpty(record.getApplicantName());
        String colleagueName = nullToEmpty(record.getColleagueName());
        String teamLeaderName = nullToEmpty(record.getTeamLeaderName());
        String moderatorName = nullToEmpty(record.getModeratorName());
        String meetingNumber = nullToEmpty(record.getMeetingNumber());

        String assessmentDateTimeText = formatNotificationDateTime(record.getApplicationDatetime());
        String moderatorDateTimeText = formatModeratorConnectDateTime(record.getApplicationDatetime());

        StringBuilder sb = new StringBuilder();
        sb.append(teacherName).append(" 老师，您好！\n");
        if (!assessmentDateTimeText.isEmpty()) {
            sb.append(assessmentDateTimeText).append("由您主评\n");
        } else {
            sb.append("由您主评\n");
        }
        sb.append("申评人:   ").append(applicantName).append('\n');
        sb.append("同事:  ").append(colleagueName).append('\n');
        sb.append("团队长: ").append(teamLeaderName).append('\n');
        sb.append("评估老师:    ").append(teacherName).append("老师\n");
        sb.append("主持人:    ").append(moderatorName);
        if (!moderatorDateTimeText.isEmpty()) {
            sb.append("  ").append(moderatorDateTimeText);
        }
        sb.append("连线☎\n\n");
        sb.append("#腾讯会议： ").append(meetingNumber).append('\n');
        sb.append("❤旁听学习须关麦静场\n");
        sb.append("❤影响干扰会被移出");
        return sb.toString();
    }

    private static String formatNotificationDateTime(LocalDateTime datetime) {
        if (datetime == null) {
            return "";
        }
        return datetime.format(NOTIFICATION_DATE_TIME_FORMATTER);
    }

    private static String formatModeratorConnectDateTime(LocalDateTime applicationDatetime) {
        if (applicationDatetime == null) {
            return "";
        }
        LocalDateTime connectTime = applicationDatetime.minus(MODERATOR_CONNECT_MINUTES_BEFORE, ChronoUnit.MINUTES);
        return connectTime.format(MODERATOR_DATE_TIME_FORMATTER);
    }

    @Override
    public void exportExcel(String tenantId,
                            String applicationDatetimeStart,
                            String applicationDatetimeEnd,
                            HttpServletResponse response) {
        try {
            if (tenantId == null || tenantId.trim().isEmpty()) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "tenantId不能为空");
                return;
            }
            if (applicationDatetimeStart == null || applicationDatetimeStart.trim().isEmpty()) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "applicationDatetimeStart不能为空");
                return;
            }
            if (applicationDatetimeEnd == null || applicationDatetimeEnd.trim().isEmpty()) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "applicationDatetimeEnd不能为空");
                return;
            }

            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            LocalDateTime start;
            LocalDateTime end;
            try {
                start = LocalDateTime.parse(applicationDatetimeStart.trim(), formatter);
            } catch (Exception e) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "申请开始时间格式错误，请使用 yyyy-MM-dd HH:mm:ss");
                return;
            }
            try {
                end = LocalDateTime.parse(applicationDatetimeEnd.trim(), formatter);
            } catch (Exception e) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "申请结束时间格式错误，请使用 yyyy-MM-dd HH:mm:ss");
                return;
            }
            if (start.isAfter(end)) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "申请开始时间不能晚于结束时间");
                return;
            }

            LambdaQueryWrapper<LbAssessmentApply> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim())
                    .ge(LbAssessmentApply::getApplicationDatetime, start)
                    .le(LbAssessmentApply::getApplicationDatetime, end)
                    .orderByDesc(LbAssessmentApply::getApplicationDatetime);
            List<LbAssessmentApply> rows = this.list(queryWrapper);

            try (Workbook workbook = new XSSFWorkbook()) {
                Sheet sheet = workbook.createSheet("申请评估");

                CellStyle headerStyle = workbook.createCellStyle();
                Font headerFont = workbook.createFont();
                headerFont.setBold(true);
                headerStyle.setFont(headerFont);
                headerStyle.setAlignment(HorizontalAlignment.CENTER);
                headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
                headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                ((XSSFCellStyle) headerStyle).setFillForegroundColor(new XSSFColor(new Color(255, 217, 102), null));
                headerStyle.setBorderTop(BorderStyle.THIN);
                headerStyle.setBorderBottom(BorderStyle.THIN);
                headerStyle.setBorderLeft(BorderStyle.THIN);
                headerStyle.setBorderRight(BorderStyle.THIN);

                CellStyle dataStyle = workbook.createCellStyle();
                dataStyle.setAlignment(HorizontalAlignment.CENTER);
                dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
                dataStyle.setBorderTop(BorderStyle.THIN);
                dataStyle.setBorderBottom(BorderStyle.THIN);
                dataStyle.setBorderLeft(BorderStyle.THIN);
                dataStyle.setBorderRight(BorderStyle.THIN);

                String[] headers = {
                        "序号", "申请人", "电话", "同事", "电话", "团队长", "全收益团队长",
                        "整理确定时间", "安排评估老师", "安排主持人", "会议号"
                };

                Row headerRow = sheet.createRow(0);
                headerRow.setHeightInPoints(22);
                for (int i = 0; i < headers.length; i++) {
                    createCell(headerRow, i, headers[i], headerStyle);
                }

                int rowIndex = 1;
                for (LbAssessmentApply item : rows) {
                    Row row = sheet.createRow(rowIndex);
                    row.setHeightInPoints(20);
                    createCell(row, 0, String.valueOf(rowIndex), dataStyle);
                    createCell(row, 1, nullToEmpty(item.getApplicantName()), dataStyle);
                    createCell(row, 2, nullToEmpty(item.getApplicantPhone()), dataStyle);
                    createCell(row, 3, nullToEmpty(item.getColleagueName()), dataStyle);
                    createCell(row, 4, nullToEmpty(item.getColleaguePhone()), dataStyle);
                    createCell(row, 5, nullToEmpty(item.getTeamLeaderName()), dataStyle);
                    createCell(row, 6, nullToEmpty(item.getTeamBigLeaderName()), dataStyle);
                    createCell(row, 7, formatApplicationDatetime(item.getApplicationDatetime()), dataStyle);
                    createCell(row, 8, nullToEmpty(item.getAssessmentTeacherName()), dataStyle);
                    createCell(row, 9, nullToEmpty(item.getModeratorName()), dataStyle);
                    createCell(row, 10, nullToEmpty(item.getMeetingNumber()), dataStyle);
                    rowIndex++;
                }

                adjustColumnWidths(sheet, headers);

                String filename = "申请评估_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
                String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8).replaceAll("\\+", "%20");

                response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded);

                try (ServletOutputStream os = response.getOutputStream()) {
                    workbook.write(os);
                    os.flush();
                }
            }
        } catch (Exception e) {
            log.error("申请评估导出异常", e);
            try {
                response.reset();
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                response.setContentType("text/plain;charset=UTF-8");
                response.getWriter().write("导出异常：" + e.getMessage());
            } catch (Exception ignored) {
                // ignore
            }
        }
    }

    private LambdaQueryWrapper<LbAssessmentApply> buildQueryWrapper(String tenantId,
                                                                      String applicantName,
                                                                      String applicantPhone,
                                                                      String colleagueName,
                                                                      String teamLeaderName,
                                                                      String teamBigLeaderName,
                                                                      String assessmentTeacherName,
                                                                      String moderatorName,
                                                                      String meetingNumber) {
        LambdaQueryWrapper<LbAssessmentApply> queryWrapper = new LambdaQueryWrapper<>();
        if (tenantId != null && !tenantId.trim().isEmpty()) {
            queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim());
        }
        if (applicantName != null && !applicantName.trim().isEmpty()) {
            queryWrapper.like(LbAssessmentApply::getApplicantName, applicantName.trim());
        }
        if (applicantPhone != null && !applicantPhone.trim().isEmpty()) {
            queryWrapper.like(LbAssessmentApply::getApplicantPhone, applicantPhone.trim());
        }
        if (colleagueName != null && !colleagueName.trim().isEmpty()) {
            queryWrapper.like(LbAssessmentApply::getColleagueName, colleagueName.trim());
        }
        if (teamLeaderName != null && !teamLeaderName.trim().isEmpty()) {
            queryWrapper.like(LbAssessmentApply::getTeamLeaderName, teamLeaderName.trim());
        }
        if (teamBigLeaderName != null && !teamBigLeaderName.trim().isEmpty()) {
            queryWrapper.like(LbAssessmentApply::getTeamBigLeaderName, teamBigLeaderName.trim());
        }
        if (assessmentTeacherName != null && !assessmentTeacherName.trim().isEmpty()) {
            queryWrapper.like(LbAssessmentApply::getAssessmentTeacherName, assessmentTeacherName.trim());
        }
        if (moderatorName != null && !moderatorName.trim().isEmpty()) {
            queryWrapper.like(LbAssessmentApply::getModeratorName, moderatorName.trim());
        }
        if (meetingNumber != null && !meetingNumber.trim().isEmpty()) {
            queryWrapper.like(LbAssessmentApply::getMeetingNumber, meetingNumber.trim());
        }
        queryWrapper.orderByDesc(LbAssessmentApply::getUpdatedAt)
                .orderByDesc(LbAssessmentApply::getCreatedAt);
        return queryWrapper;
    }

    private static String nullToEmpty(String value) {
        return value == null ? "" : value;
    }

    private static String formatApplicationDatetime(LocalDateTime datetime) {
        if (datetime == null) {
            return "";
        }
        if (datetime.getMinute() == 0 && datetime.getSecond() == 0) {
            return datetime.getMonthValue() + "." + datetime.getDayOfMonth() + "日" + datetime.getHour() + "点";
        }
        return datetime.format(DateTimeFormatter.ofPattern("M月d日 H:mm"));
    }

    private static void createCell(Row row, int col, String text, CellStyle style) {
        Cell cell = row.createCell(col);
        cell.setCellValue(text);
        cell.setCellStyle(style);
    }

    /**
     * 按单元格内容计算列宽。POI 的 autoSizeColumn 对中文支持差，改为按字符宽度估算。
     */
    private static void adjustColumnWidths(Sheet sheet, String[] headers) {
        int columnCount = headers.length;
        int[] maxWidths = new int[columnCount];
        for (int i = 0; i < columnCount; i++) {
            maxWidths[i] = calcDisplayWidth(headers[i]);
        }
        for (int r = 1; r <= sheet.getLastRowNum(); r++) {
            Row row = sheet.getRow(r);
            if (row == null) {
                continue;
            }
            for (int c = 0; c < columnCount; c++) {
                Cell cell = row.getCell(c);
                if (cell != null && cell.getCellType() == CellType.STRING) {
                    maxWidths[c] = Math.max(maxWidths[c], calcDisplayWidth(cell.getStringCellValue()));
                }
            }
        }
        for (int i = 0; i < columnCount; i++) {
            // 表头加粗，额外留 1 个字符边距
            int width = Math.min(255 * 256, (maxWidths[i] + 3) * 256);
            sheet.setColumnWidth(i, width);
        }
    }

    private static int calcDisplayWidth(String text) {
        if (text == null || text.isEmpty()) {
            return 0;
        }
        int width = 0;
        for (char ch : text.toCharArray()) {
            width += ch > 127 ? 2 : 1;
        }
        return width;
    }
}
