package org.dromara.main.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.factory.OssFactory;
import org.dromara.main.domain.MainBackCandidate;
import org.dromara.main.domain.MainBackCategory;
import org.dromara.main.domain.MainBackClause;
import org.dromara.main.domain.MainBackOrder;
import org.dromara.main.domain.MainBackRecord;
import org.dromara.main.domain.MainOrder;
import org.dromara.main.domain.MainPosition;
import org.dromara.main.domain.MainPostCandidateReview;
import org.dromara.main.domain.MainBackInterview;
import org.dromara.main.domain.bo.MainBackOrderBo;
import org.dromara.main.domain.bo.PortalCheckOrderCreateBo;
import org.dromara.main.domain.MainStudent;
import org.dromara.main.domain.MainStudentEducation;
import org.dromara.main.domain.MainStudentExperience;
import org.dromara.main.domain.Payment;
import org.dromara.main.domain.vo.MainBackClauseVo;
import org.dromara.main.domain.vo.MainBackOrderCandidateVo;
import org.dromara.main.domain.vo.MainBackOrderVo;
import org.dromara.main.mapper.MainBackCandidateMapper;
import org.dromara.main.mapper.MainBackCategoryMapper;
import org.dromara.main.mapper.MainBackClauseMapper;
import org.dromara.main.mapper.MainBackInterviewMapper;
import org.dromara.main.mapper.MainBackOrderMapper;
import org.dromara.main.mapper.MainBackRecordMapper;
import org.dromara.main.mapper.MainOrderMapper;
import org.dromara.main.mapper.MainPositionMapper;
import org.dromara.main.mapper.MainPostCandidateReviewMapper;
import org.dromara.main.mapper.MainStudentEducationMapper;
import org.dromara.main.mapper.MainStudentExperienceMapper;
import org.dromara.main.mapper.MainStudentMapper;
import org.dromara.main.mapper.PaymentMapper;
import org.dromara.main.service.ICompanyAccountService;
import org.dromara.main.service.IMainBackOrderService;
import org.dromara.system.domain.SysTenant;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.mapper.SysTenantMapper;
import org.dromara.system.service.ISysTenantService;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class MainBackOrderServiceImpl implements IMainBackOrderService {

    private static final String RECORD_STATUS_PENDING_AUTH = "未完成";
    private static final String RECORD_STATUS_AUTHORIZED = "已授权";
    private static final String RECORD_STATUS_UNAUTHORIZED = "未授权";
    private static final DateTimeFormatter REPORT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

    private static final Set<String> AUTHORIZATION_TERMINAL_STATUSES =
        new HashSet<>(List.of(RECORD_STATUS_AUTHORIZED, RECORD_STATUS_UNAUTHORIZED));

    private final MainBackOrderMapper baseMapper;
    private final MainBackRecordMapper recordMapper;
    private final MainOrderMapper mainOrderMapper;
    private final MainBackCategoryMapper mainBackCategoryMapper;
    private final MainBackClauseMapper mainBackClauseMapper;
    private final MainBackCandidateMapper mainBackCandidateMapper;
    private final MainStudentMapper mainStudentMapper;
    private final MainStudentEducationMapper mainStudentEducationMapper;
    private final MainStudentExperienceMapper mainStudentExperienceMapper;
    private final MainPostCandidateReviewMapper mainPostCandidateReviewMapper;
    private final MainBackInterviewMapper mainBackInterviewMapper;
    private final MainPositionMapper mainPositionMapper;
    private final PaymentMapper paymentMapper;
    private final SysTenantMapper sysTenantMapper;
    private final ISysTenantService tenantService;
    private final ICompanyAccountService companyAccountService;
    private final WordReportService wordReportService;

    @Override
    public TableDataInfo<MainBackOrderVo> queryPageList(MainBackOrderBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<MainBackOrder> lqw = new LambdaQueryWrapper<>();

        // 1. 左侧分类过滤
        lqw.eq(bo.getCategoryId() != null, MainBackOrder::getCategoryId, bo.getCategoryId());

        // 2. 状态过滤
        lqw.eq(StrUtil.isNotBlank(bo.getStatus()), MainBackOrder::getStatus, bo.getStatus());

        // 3. 右侧单位名称搜索逻辑
        if (StrUtil.isNotBlank(bo.getKeyword())) {
            // 通过单位名称模糊查询租户ID列表
            SysTenantBo tenantBo = new SysTenantBo();
            tenantBo.setCompanyName(bo.getKeyword());
            List<String> tenantIds = tenantService.queryList(tenantBo)
                .stream()
                .map(SysTenantVo::getTenantId)
                .toList();

            if (tenantIds.isEmpty()) {
                return TableDataInfo.build(new ArrayList<>());
            }
            lqw.in(MainBackOrder::getTenantId, tenantIds);
        }

        // 4. 分页查询订单主表
        Page<MainBackOrderVo> pageResult = baseMapper.selectVoPage(pageQuery.build(), lqw);
        TableDataInfo<MainBackOrderVo> result = TableDataInfo.build(pageResult);

        // 5. 动态填充页面所需字段
        for (MainBackOrderVo vo : result.getRows()) {
            // A. 统计候选人人数
            Long count = recordMapper.selectCount(
                new LambdaQueryWrapper<MainBackRecord>()
                    .eq(MainBackRecord::getOrderId, vo.getId())
            );
            vo.setCandidateCount(count);

            // B. 填充企业名称
            SysTenantVo tenant = tenantService.queryByTenantId(vo.getTenantId());
            if (tenant != null) {
                vo.setCompanyName(tenant.getCompanyName());
            }
        }

        return result;
    }

    @Override
    public List<MainBackOrderVo> queryPortalListByTenantId(String tenantId) {
        LambdaQueryWrapper<MainOrder> orderLqw = Wrappers.lambdaQuery();
        orderLqw.eq(StrUtil.isNotBlank(tenantId), MainOrder::getTenantId, tenantId)
            .in(MainOrder::getOrderType, List.of(3, 4))
            .isNotNull(MainOrder::getBusinessId)
            .orderByDesc(MainOrder::getCreateTime);

        List<MainOrder> mainOrders = mainOrderMapper.selectList(orderLqw);
        if (mainOrders.isEmpty()) {
            return new ArrayList<>();
        }

        List<Long> businessIds = mainOrders.stream()
            .map(MainOrder::getBusinessId)
            .distinct()
            .collect(Collectors.toList());

        Map<Long, MainBackOrder> backOrderMap = new LinkedHashMap<>();
        for (MainBackOrder backOrder : baseMapper.selectBatchIds(businessIds)) {
            backOrderMap.put(backOrder.getId(), backOrder);
        }

        List<MainBackOrderVo> result = new ArrayList<>();
        for (MainOrder mainOrder : mainOrders) {
            MainBackOrder backOrder = backOrderMap.get(mainOrder.getBusinessId());
            if (backOrder == null) {
                continue;
            }
            MainBackOrderVo vo = MapstructUtils.convert(backOrder, MainBackOrderVo.class);
            fillPortalFields(vo, mainOrder);
            result.add(vo);
        }
        return result;
    }

    @Override
    public MainBackOrderVo queryPortalDetail(String orderNo, String tenantId) {
        MainOrder mainOrder = mainOrderMapper.selectOne(
            Wrappers.<MainOrder>lambdaQuery()
                .eq(MainOrder::getOrderNo, orderNo)
                .eq(MainOrder::getTenantId, tenantId)
                .last("limit 1")
        );
        if (mainOrder == null) {
            return null;
        }
        if (mainOrder.getBusinessId() == null) {
            return null;
        }

        MainBackOrder backOrder = baseMapper.selectById(mainOrder.getBusinessId());
        if (backOrder == null) {
            return null;
        }

        MainBackOrderVo vo = MapstructUtils.convert(backOrder, MainBackOrderVo.class);
        fillPortalFields(vo, mainOrder);
        vo.setPayment(paymentMapper.selectVoOne(
            Wrappers.<Payment>lambdaQuery()
                .eq(Payment::getOrderId, mainOrder.getId())
                .orderByDesc(Payment::getCreateTime)
                .last("limit 1"),
            false
        ));
        if (vo.getPayment() != null) {
            vo.getPayment().setPaymentStatusText(toPaymentStatusText(vo.getPayment().getPaymentStatus()));
        }
        vo.setCandidates(buildPortalCandidates(backOrder.getId()));
        vo.setClauses(buildPortalClauses(backOrder));
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MainBackOrderVo createPortalOrder(String tenantId, PortalCheckOrderCreateBo bo) {
        if (StringUtils.isBlank(tenantId)) {
            throw new ServiceException("未登录或token已失效");
        }
        if (bo.getCandidates() == null || bo.getCandidates().isEmpty()) {
            throw new ServiceException("候选人列表不能为空");
        }

        SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
        if (tenant == null) {
            throw new ServiceException("企业信息不存在");
        }

        List<Long> clauseIds;
        String clauseIdsJson;
        String clauseGroupKey = null;
        Long categoryId = null;
        String categoryName;
        BigDecimal unitPrice;

        if (Integer.valueOf(1).equals(bo.getOrderType())) {
            if (bo.getCategoryId() == null) {
                throw new ServiceException("套餐ID不能为空");
            }
            MainBackCategory category = mainBackCategoryMapper.selectById(bo.getCategoryId());
            if (category == null || !"1".equals(category.getStatus())) {
                throw new ServiceException("背调套餐不存在或已停用");
            }

            List<MainBackClause> clauses = mainBackClauseMapper.selectList(
                Wrappers.<MainBackClause>lambdaQuery()
                    .eq(MainBackClause::getCategoryId, category.getId())
                    .eq(MainBackClause::getStatus, "1")
                    .orderByAsc(MainBackClause::getId)
            );
            if (clauses.isEmpty()) {
                throw new ServiceException("该套餐下暂无可用条款");
            }

            categoryId = category.getId();
            categoryName = category.getName();
            unitPrice = defaultAmount(category.getPrice());
            clauseIds = clauses.stream().map(MainBackClause::getId).toList();
            clauseIdsJson = JsonUtils.toJsonString(clauseIds);
        } else if (Integer.valueOf(2).equals(bo.getOrderType())) {
            if (StringUtils.isBlank(bo.getClauseIds())) {
                throw new ServiceException("自定义条款不能为空");
            }
            List<Long> parsedClauseIds = JsonUtils.parseArray(bo.getClauseIds(), Long.class);
            if (parsedClauseIds == null || parsedClauseIds.isEmpty()) {
                throw new ServiceException("自定义条款不能为空");
            }

            clauseIds = parsedClauseIds.stream().distinct().sorted().collect(Collectors.toList());
            List<MainBackClause> clauses = mainBackClauseMapper.selectBatchIds(clauseIds).stream()
                .filter(clause -> clause != null && "1".equals(clause.getStatus()))
                .sorted(Comparator.comparing(MainBackClause::getId))
                .toList();
            if (clauses.size() != clauseIds.size()) {
                throw new ServiceException("部分自定义条款不存在或已停用");
            }

            unitPrice = clauses.stream()
                .map(MainBackClause::getPrice)
                .filter(java.util.Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
            categoryName = "自定义背调组合";
            clauseIdsJson = JsonUtils.toJsonString(clauseIds);
            clauseGroupKey = DigestUtil.md5Hex(clauseIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
        } else {
            throw new ServiceException("不支持的订单类型");
        }

        int candidateCount = bo.getCandidates().size();
        BigDecimal totalAmount = unitPrice.multiply(BigDecimal.valueOf(candidateCount));
        if (totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("订单金额必须大于0");
        }

        List<Long> candidateRelationIds = new ArrayList<>(candidateCount);
        for (PortalCheckOrderCreateBo.CandidateInfo candidateInfo : bo.getCandidates()) {
            MainStudent student = matchStudent(candidateInfo);
            MainBackCandidate relation = findOrCreateCandidateRelation(tenantId, student.getId());
            candidateRelationIds.add(relation.getId());
        }

        String orderNo = generateOrderNo();

        MainBackOrder backOrder = new MainBackOrder();
        backOrder.setOrderNo(orderNo);
        backOrder.setTenantId(tenantId);
        backOrder.setCategoryId(categoryId);
        backOrder.setCategoryName(categoryName);
        backOrder.setTotalAmount(totalAmount);
        backOrder.setClauseIds(clauseIdsJson);
        backOrder.setClauseGroupKey(clauseGroupKey);
        backOrder.setStatus("0");
        baseMapper.insert(backOrder);

        MainOrder mainOrder = new MainOrder();
        mainOrder.setOrderNo(orderNo);
        mainOrder.setOrderType(3);
        mainOrder.setBuyerType(1);
        mainOrder.setBuyerId(tenant.getId());
        mainOrder.setBuyerName(tenant.getCompanyName());
        mainOrder.setSellerId(1L);
        mainOrder.setTotalAmount(totalAmount);
        mainOrder.setPaidAmount(BigDecimal.ZERO);
        mainOrder.setRefundAmount(BigDecimal.ZERO);
        mainOrder.setOrderStatus(0);
        mainOrder.setPayStatus(0);
        mainOrder.setBusinessId(backOrder.getId());
        mainOrder.setTenantId(tenantId);
        mainOrderMapper.insert(mainOrder);

        for (Long candidateRelationId : candidateRelationIds) {
            MainBackRecord record = new MainBackRecord();
            record.setOrderId(backOrder.getId());
            record.setCandidateId(candidateRelationId);
            record.setStatus("未完成");
            recordMapper.insert(record);
        }

        MainBackOrderVo vo = MapstructUtils.convert(backOrder, MainBackOrderVo.class);
        vo.setOrderId(mainOrder.getId());
        vo.setOrderNo(mainOrder.getOrderNo());
        vo.setCompanyId(tenant.getId());
        vo.setCompanyName(tenant.getCompanyName());
        vo.setTenantId(tenantId);
        vo.setOrderType(bo.getOrderType());
        vo.setClauseIds(clauseIdsJson);
        vo.setUnitPrice(unitPrice);
        vo.setTotalAmount(totalAmount);
        vo.setOrderStatus(0);
        vo.setMainOrderStatus(0);
        vo.setCandidateCount((long) candidateCount);
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean payOrderByBalance(Long orderId, String tenantId) {
        MainOrder mainOrder = mainOrderMapper.selectById(orderId);
        if (mainOrder == null) {
            throw new ServiceException("订单不存在");
        }
        if (!StringUtils.equals(mainOrder.getTenantId(), tenantId)) {
            throw new ServiceException("无权操作该订单");
        }
        if (mainOrder.getOrderStatus() == null || mainOrder.getOrderStatus() != 0) {
            throw new ServiceException("订单状态不正确，无法支付");
        }
        if (mainOrder.getPayStatus() != null && mainOrder.getPayStatus() != 0) {
            throw new ServiceException("订单已支付或已关闭");
        }
        if (mainOrder.getBusinessId() == null) {
            throw new ServiceException("背调订单不存在");
        }

        MainBackOrder backOrder = baseMapper.selectById(mainOrder.getBusinessId());
        if (backOrder == null) {
            throw new ServiceException("背调订单不存在");
        }

        SysTenant tenant = sysTenantMapper.selectOne(
            Wrappers.<SysTenant>lambdaQuery()
                .eq(SysTenant::getTenantId, tenantId)
                .last("limit 1")
        );
        if (tenant == null) {
            throw new ServiceException("企业信息不存在");
        }

        BigDecimal totalAmount = mainOrder.getTotalAmount() == null ? BigDecimal.ZERO : mainOrder.getTotalAmount();
        if (defaultAmount(tenant.getAvailableBalance()).compareTo(totalAmount) < 0) {
            throw new ServiceException("可用余额不足，请充值或选择支付宝支付");
        }
        companyAccountService.payByBalance(
            tenant.getId(),
            totalAmount,
            mainOrder.getId(),
            mainOrder.getOrderNo(),
            "背调订单支付：" + mainOrder.getOrderNo()
        );

        Payment payment = new Payment();
        payment.setPaymentNo("PAY" + cn.hutool.core.date.DateUtil.format(new Date(), "yyyyMMddHHmmss")
            + cn.hutool.core.util.RandomUtil.randomNumbers(4));
        payment.setOrderId(orderId);
        payment.setOrderNo(mainOrder.getOrderNo());
        payment.setPaymentType(3);
        payment.setPaymentMethod(3);
        payment.setPaymentAmount(totalAmount);
        payment.setPaymentStatus(2);
        payment.setPayTime(new Date());
        payment.setRemark("余额支付");
        paymentMapper.insert(payment);

        mainOrder.setOrderStatus(1);
        mainOrder.setPayStatus(2);
        mainOrder.setPaidAmount(totalAmount);
        mainOrder.setPayTime(new Date());
        mainOrderMapper.updateById(mainOrder);

        backOrder.setStatus("1");
        baseMapper.updateById(backOrder);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean completeOrder(Long id) {
        MainBackOrder backOrder = baseMapper.selectById(id);
        if (backOrder == null) {
            throw new ServiceException("背调订单不存在");
        }
        if ("2".equals(backOrder.getStatus())) {
            throw new ServiceException("订单已完成，无需重复操作");
        }
        if ("3".equals(backOrder.getStatus())) {
            throw new ServiceException("订单已退款，无法完成");
        }

        MainOrder mainOrder = mainOrderMapper.selectOne(
            Wrappers.<MainOrder>lambdaQuery()
                .eq(MainOrder::getBusinessId, id)
                .eq(MainOrder::getOrderType, 3)
                .last("limit 1")
        );
        if (mainOrder == null) {
            throw new ServiceException("订单不存在");
        }
        if (mainOrder.getPayStatus() == null || mainOrder.getPayStatus() != 2) {
            throw new ServiceException("订单未支付，无法完成");
        }

        List<MainBackRecord> records = recordMapper.selectList(
            Wrappers.<MainBackRecord>lambdaQuery()
                .eq(MainBackRecord::getOrderId, id)
        );
        if (records.isEmpty()) {
            throw new ServiceException("订单下暂无候选人记录，无法完成");
        }
        boolean hasUnfinished = records.stream()
            .anyMatch(record -> !"已完成".equals(record.getStatus()) && !"完成".equals(record.getStatus()));
        if (hasUnfinished) {
            throw new ServiceException("仍有候选人未完成背调，无法完成订单");
        }

        Payment latestPayment = queryLatestPayment(mainOrder.getId());
        if (isBalancePayment(latestPayment)) {
            companyAccountService.deductInUseBalance(
                mainOrder.getBuyerId(),
                defaultAmount(mainOrder.getTotalAmount()),
                mainOrder.getId(),
                mainOrder.getOrderNo(),
                "订单完成扣款：" + mainOrder.getOrderNo()
            );
        }

        backOrder.setStatus("2");
        baseMapper.updateById(backOrder);

        mainOrder.setOrderStatus(2);
        mainOrder.setCompleteTime(new Date());
        mainOrderMapper.updateById(mainOrder);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MainBackOrderCandidateVo confirmPortalCandidateAuthorization(Long recordId, String tenantId) {
        if (recordId == null) {
            throw new ServiceException("候选人记录不能为空");
        }
        if (StringUtils.isBlank(tenantId)) {
            throw new ServiceException("未登录或token已失效");
        }

        MainBackRecord record = recordMapper.selectById(recordId);
        if (record == null) {
            throw new ServiceException("候选人记录不存在");
        }

        MainBackOrder backOrder = baseMapper.selectById(record.getOrderId());
        if (backOrder == null || !StringUtils.equals(backOrder.getTenantId(), tenantId)) {
            throw new ServiceException("无权操作该候选人授权记录");
        }

        MainOrder mainOrder = mainOrderMapper.selectOne(
            Wrappers.<MainOrder>lambdaQuery()
                .eq(MainOrder::getBusinessId, backOrder.getId())
                .eq(MainOrder::getOrderType, 3)
                .eq(MainOrder::getTenantId, tenantId)
                .last("limit 1")
        );
        if (mainOrder == null) {
            throw new ServiceException("订单不存在");
        }

        if (!RECORD_STATUS_AUTHORIZED.equals(record.getStatus())) {
            MainBackRecord update = new MainBackRecord();
            update.setId(record.getId());
            update.setStatus(RECORD_STATUS_AUTHORIZED);
            update.setFinishTime(LocalDateTime.now());
            recordMapper.updateById(update);
            record.setStatus(RECORD_STATUS_AUTHORIZED);
            record.setFinishTime(update.getFinishTime());
        }

        completePortalOrderWhenAuthorizationFinished(backOrder, mainOrder);
        return buildPortalCandidateVo(record);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String generatePortalCandidateReport(Long recordId, String tenantId) {
        if (recordId == null) {
            throw new ServiceException("候选人记录不能为空");
        }
        if (StringUtils.isBlank(tenantId)) {
            throw new ServiceException("未登录或token已失效");
        }

        MainBackRecord record = recordMapper.selectById(recordId);
        if (record == null) {
            throw new ServiceException("候选人记录不存在");
        }

        MainBackOrder backOrder = baseMapper.selectById(record.getOrderId());
        if (backOrder == null || !StringUtils.equals(backOrder.getTenantId(), tenantId)) {
            throw new ServiceException("无权查看该候选人的报告");
        }

        if (record.getReportStatus() == null || record.getReportStatus() != 1) {
            throw new ServiceException("报告尚未填写完毕，暂时无法生成和查看！");
        }

        if (StringUtils.isNotBlank(record.getReportUrl())) {
            return record.getReportUrl();
        }

        ReportContext context = buildReportContext(record, backOrder);
        byte[] pdfBytes = buildReportPdf(context);
        String reportUrl = uploadReportPdf(record, context, pdfBytes);

        MainBackRecord update = new MainBackRecord();
        update.setId(record.getId());
        update.setReportUrl(reportUrl);
        recordMapper.updateById(update);
        return reportUrl;
    }

    @Override
    public void downloadPortalCandidateReport(Long recordId, String tenantId, HttpServletResponse response) {
        MainBackRecord record = getPortalReportRecord(recordId, tenantId);
        String reportUrl = StringUtils.isNotBlank(record.getReportUrl())
            ? record.getReportUrl()
            : generatePortalCandidateReport(recordId, tenantId);

        MainBackCandidate candidate = mainBackCandidateMapper.selectById(record.getCandidateId());
        MainStudent student = candidate == null ? null : mainStudentMapper.selectById(candidate.getStudentId());
        String fileName = sanitizeFileName(
            safe(student == null ? null : student.getName()),
            "candidate-report"
        ) + ".pdf";

        try {
            FileUtils.setAttachmentResponseHeader(response, fileName);
            response.setContentType(MediaType.APPLICATION_PDF_VALUE);
            OssClient ossClient = OssFactory.instance();
            ossClient.download(ossClient.removeBaseUrl(reportUrl), response.getOutputStream(), response::setContentLengthLong);
        } catch (Exception e) {
            throw new ServiceException("下载报告失败: " + e.getMessage());
        }
    }

    private void fillPortalFields(MainBackOrderVo vo, MainOrder mainOrder) {
        Long count = recordMapper.selectCount(
            new LambdaQueryWrapper<MainBackRecord>()
                .eq(MainBackRecord::getOrderId, vo.getId())
        );
        vo.setCandidateCount(count);
        vo.setOrderId(mainOrder.getId());
        vo.setOrderNo(mainOrder.getOrderNo());
        vo.setCompanyId(mainOrder.getBuyerId());
        vo.setTenantId(mainOrder.getTenantId());
        vo.setTotalAmount(mainOrder.getTotalAmount());
        vo.setCreateTime(mainOrder.getCreateTime() == null ? null :
            mainOrder.getCreateTime().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime());
        vo.setOrderType(vo.getCategoryId() != null ? 1 : 2);
        vo.setOrderStatus(parseIntOrDefault(vo.getStatus(), 0));
        vo.setMainOrderStatus(mainOrder.getOrderStatus());

        if (count != null && count > 0 && vo.getTotalAmount() != null) {
            vo.setUnitPrice(vo.getTotalAmount().divide(BigDecimal.valueOf(count), 2, java.math.RoundingMode.HALF_UP));
        } else {
            vo.setUnitPrice(BigDecimal.ZERO);
        }
    }

    private MainBackRecord getPortalReportRecord(Long recordId, String tenantId) {
        if (recordId == null) {
            throw new ServiceException("候选人记录不能为空");
        }
        if (StringUtils.isBlank(tenantId)) {
            throw new ServiceException("未登录或token已失效");
        }

        MainBackRecord record = recordMapper.selectById(recordId);
        if (record == null) {
            throw new ServiceException("候选人记录不存在");
        }

        MainBackOrder backOrder = baseMapper.selectById(record.getOrderId());
        if (backOrder == null || !StringUtils.equals(backOrder.getTenantId(), tenantId)) {
            throw new ServiceException("无权查看该候选人的报告");
        }

        if (record.getReportStatus() == null || record.getReportStatus() != 1) {
            throw new ServiceException("报告尚未填写完毕，暂时无法生成和查看！");
        }

        return record;
    }

    private MainStudent matchStudent(PortalCheckOrderCreateBo.CandidateInfo candidateInfo) {
        MainStudent student = mainStudentMapper.selectOne(
            Wrappers.<MainStudent>lambdaQuery()
                .eq(MainStudent::getMobile, candidateInfo.getCandidatePhone())
                .eq(MainStudent::getIdCardNumber, candidateInfo.getCandidateIdNumber())
                .eq(MainStudent::getStatus, "0")
                .last("limit 1")
        );
        if (student == null) {
            throw new ServiceException(
                "候选人【" + candidateInfo.getCandidateName() + "】未在学员库中找到，请先完成学员注册"
            );
        }
        return student;
    }

    private MainBackCandidate findOrCreateCandidateRelation(String tenantId, Long studentId) {
        MainBackCandidate relation = mainBackCandidateMapper.selectOne(
            Wrappers.<MainBackCandidate>lambdaQuery()
                .eq(MainBackCandidate::getTenantId, tenantId)
                .eq(MainBackCandidate::getStudentId, studentId)
                .last("limit 1")
        );
        if (relation != null) {
            return relation;
        }

        relation = new MainBackCandidate();
        relation.setTenantId(tenantId);
        relation.setStudentId(studentId);
        relation.setSource("portal");
        mainBackCandidateMapper.insert(relation);
        return relation;
    }

    private BigDecimal defaultAmount(BigDecimal amount) {
        return amount == null ? BigDecimal.ZERO : amount;
    }

    private void completePortalOrderWhenAuthorizationFinished(MainBackOrder backOrder, MainOrder mainOrder) {
        List<MainBackRecord> records = recordMapper.selectList(
            Wrappers.<MainBackRecord>lambdaQuery()
                .eq(MainBackRecord::getOrderId, backOrder.getId())
        );
        if (records.isEmpty()) {
            return;
        }

        boolean allAuthorizationCompleted = records.stream()
            .map(MainBackRecord::getStatus)
            .allMatch(AUTHORIZATION_TERMINAL_STATUSES::contains);
        if (!allAuthorizationCompleted) {
            return;
        }

        if (!"2".equals(backOrder.getStatus())) {
            MainBackOrder orderUpdate = new MainBackOrder();
            orderUpdate.setId(backOrder.getId());
            orderUpdate.setStatus("2");
            baseMapper.updateById(orderUpdate);
        }

        if (!Integer.valueOf(2).equals(mainOrder.getOrderStatus())) {
            MainOrder mainOrderUpdate = new MainOrder();
            mainOrderUpdate.setId(mainOrder.getId());
            mainOrderUpdate.setOrderStatus(2);
            mainOrderUpdate.setCompleteTime(new Date());
            mainOrderMapper.updateById(mainOrderUpdate);
        }
    }

    private MainBackOrderCandidateVo buildPortalCandidateVo(MainBackRecord record) {
        MainBackCandidate candidate = mainBackCandidateMapper.selectById(record.getCandidateId());
        MainStudent student = candidate == null ? null : mainStudentMapper.selectById(candidate.getStudentId());

        MainBackOrderCandidateVo vo = new MainBackOrderCandidateVo();
        vo.setId(record.getId());
        vo.setOrderId(record.getOrderId());
        vo.setStudentName(student == null ? null : student.getName());
        vo.setCandidatePhone(student == null ? null : student.getMobile());
        vo.setCandidateIdType("1");
        vo.setCandidateIdNumber(student == null ? null : student.getIdCardNumber());
        vo.setStatus(mapRecordStatus(record.getStatus()));
        vo.setStatusText(mapRecordStatusText(record.getStatus()));
        vo.setReportUrl(record.getReportUrl());
        return vo;
    }

    private Payment queryLatestPayment(Long orderId) {
        return paymentMapper.selectOne(
            Wrappers.<Payment>lambdaQuery()
                .eq(Payment::getOrderId, orderId)
                .orderByDesc(Payment::getCreateTime)
                .last("limit 1")
        );
    }

    private boolean isBalancePayment(Payment payment) {
        return payment == null || Integer.valueOf(3).equals(payment.getPaymentMethod());
    }

    private List<MainBackOrderCandidateVo> buildPortalCandidates(Long backOrderId) {
        List<MainBackRecord> records = recordMapper.selectList(
            Wrappers.<MainBackRecord>lambdaQuery()
                .eq(MainBackRecord::getOrderId, backOrderId)
                .orderByAsc(MainBackRecord::getCreateTime)
        );
        if (records.isEmpty()) {
            return new ArrayList<>();
        }

        List<Long> candidateIds = records.stream()
            .map(MainBackRecord::getCandidateId)
            .distinct()
            .toList();
        Map<Long, MainBackCandidate> candidateMap = new HashMap<>();
        for (MainBackCandidate candidate : mainBackCandidateMapper.selectBatchIds(candidateIds)) {
            candidateMap.put(candidate.getId(), candidate);
        }

        List<Long> studentIds = candidateMap.values().stream()
            .map(MainBackCandidate::getStudentId)
            .filter(java.util.Objects::nonNull)
            .distinct()
            .toList();
        Map<Long, MainStudent> studentMap = new HashMap<>();
        for (MainStudent student : mainStudentMapper.selectBatchIds(studentIds)) {
            studentMap.put(student.getId(), student);
        }

        List<MainBackOrderCandidateVo> result = new ArrayList<>(records.size());
        for (MainBackRecord record : records) {
            MainBackCandidate candidate = candidateMap.get(record.getCandidateId());
            MainStudent student = candidate == null ? null : studentMap.get(candidate.getStudentId());

            MainBackOrderCandidateVo vo = new MainBackOrderCandidateVo();
            vo.setId(record.getId());
            vo.setOrderId(backOrderId);
            vo.setStudentName(student == null ? null : student.getName());
            vo.setCandidatePhone(student == null ? null : student.getMobile());
            vo.setCandidateIdType("1");
            vo.setCandidateIdNumber(student == null ? null : student.getIdCardNumber());
            vo.setStatus(mapRecordStatus(record.getStatus()));
            vo.setStatusText(mapRecordStatusText(record.getStatus()));
            vo.setReportUrl(record.getReportUrl());
            result.add(vo);
        }
        return result;
    }

    private List<MainBackClauseVo> buildPortalClauses(MainBackOrder backOrder) {
        if (backOrder.getCategoryId() != null) {
            return mainBackClauseMapper.selectVoList(
                Wrappers.<MainBackClause>lambdaQuery()
                    .eq(MainBackClause::getCategoryId, backOrder.getCategoryId())
                    .eq(MainBackClause::getStatus, "1")
                    .orderByAsc(MainBackClause::getId)
            );
        }
        if (StringUtils.isBlank(backOrder.getClauseIds())) {
            return new ArrayList<>();
        }

        List<Long> clauseIds = JsonUtils.parseArray(backOrder.getClauseIds(), Long.class);
        if (clauseIds == null || clauseIds.isEmpty()) {
            return new ArrayList<>();
        }

        Map<Long, MainBackClauseVo> clauseMap = mainBackClauseMapper.selectVoList(
            Wrappers.<MainBackClause>lambdaQuery().in(MainBackClause::getId, clauseIds)
        ).stream().collect(Collectors.toMap(MainBackClauseVo::getId, item -> item, (a, b) -> a));

        List<MainBackClauseVo> result = new ArrayList<>();
        for (Long clauseId : clauseIds) {
            MainBackClauseVo clause = clauseMap.get(clauseId);
            if (clause != null) {
                result.add(clause);
            }
        }
        return result;
    }

    private String generateOrderNo() {
        return "ORD" + cn.hutool.core.date.DateUtil.format(new Date(), "yyyyMMddHHmmss")
            + cn.hutool.core.util.RandomUtil.randomNumbers(4);
    }

    private Integer mapRecordStatus(String status) {
        if (StringUtils.isBlank(status)) {
            return 0;
        }
        return switch (status) {
            case "进行中" -> 1;
            case "已完成", "完成" -> 2;
            case "已授权" -> 4;
            case "未授权" -> 5;
            case "已取消", "失败" -> 3;
            default -> 0;
        };
    }

    private String mapRecordStatusText(String status) {
        if (StringUtils.isBlank(status)) {
            return "待授权";
        }
        return switch (status) {
            case RECORD_STATUS_PENDING_AUTH -> "待授权";
            case RECORD_STATUS_AUTHORIZED -> RECORD_STATUS_AUTHORIZED;
            case RECORD_STATUS_UNAUTHORIZED -> RECORD_STATUS_UNAUTHORIZED;
            case "进行中" -> "背调中";
            case "已完成", "完成" -> "已完成";
            case "已取消", "失败" -> "已取消";
            default -> "待授权";
        };
    }

    private String toPaymentStatusText(Integer status) {
        if (status == null) {
            return "未知";
        }
        return switch (status) {
            case 0 -> "待支付";
            case 1 -> "支付中";
            case 2 -> "支付成功";
            case 3 -> "支付失败";
            default -> "未知";
        };
    }

    private Integer parseIntOrDefault(String value, int defaultValue) {
        if (StringUtils.isBlank(value)) {
            return defaultValue;
        }
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException ignored) {
            return defaultValue;
        }
    }

    private ReportContext buildReportContext(MainBackRecord record, MainBackOrder backOrder) {
        MainBackCandidate candidate = mainBackCandidateMapper.selectById(record.getCandidateId());
        MainStudent student = candidate == null ? null : mainStudentMapper.selectById(candidate.getStudentId());
        MainPostCandidateReview review = mainPostCandidateReviewMapper.selectOne(
            Wrappers.<MainPostCandidateReview>lambdaQuery()
                .eq(MainPostCandidateReview::getCandidateId, record.getCandidateId())
                .last("limit 1")
        );
        MainStudentEducation education = candidate == null ? null : mainStudentEducationMapper.selectOne(
            Wrappers.<MainStudentEducation>lambdaQuery()
                .eq(MainStudentEducation::getStudentId, candidate.getStudentId())
                .orderByDesc(MainStudentEducation::getEndTime)
                .orderByDesc(MainStudentEducation::getCreateTime)
                .last("limit 1")
        );
        MainStudentExperience experience = candidate == null ? null : mainStudentExperienceMapper.selectOne(
            Wrappers.<MainStudentExperience>lambdaQuery()
                .eq(MainStudentExperience::getStudentId, candidate.getStudentId())
                .orderByDesc(MainStudentExperience::getEndTime)
                .orderByDesc(MainStudentExperience::getCreateTime)
                .last("limit 1")
        );

        List<MainBackInterview> interviews = mainBackInterviewMapper.selectList(
            Wrappers.<MainBackInterview>lambdaQuery()
                .eq(MainBackInterview::getRecordId, record.getId())
                .orderByAsc(MainBackInterview::getId)
        );

        Long postId = review != null && review.getPostId() != null ? review.getPostId() : candidate == null ? null : candidate.getPostId();
        MainPosition position = postId == null ? null : mainPositionMapper.selectById(postId);
        SysTenantVo tenant = tenantService.queryByTenantId(backOrder.getTenantId());

        ReportContext context = new ReportContext();
        context.student = student;
        context.review = review;
        context.education = education;
        context.experience = experience;
        context.interviews = interviews;
        context.position = position;
        context.record = record;
        context.backOrder = backOrder;
        context.companyName = tenant == null ? null : tenant.getCompanyName();
        return context;
    }

    private byte[] buildReportPdf(ReportContext context) {
        Map<String, String> placeholders = new LinkedHashMap<>();

        // 候选人基本信息
        placeholders.put("candidateName", safe(context.student == null ? null : context.student.getName()));
        placeholders.put("position", safe(resolvePositionName(context)));
        placeholders.put("surveyTime", safe(resolveSurveyTime(context)));

        // 学历核实
        placeholders.put("school", safe(resolveSchoolName(context)));
        placeholders.put("certNo", safe(context.education == null ? null : context.education.getCertNo()));
        placeholders.put("eduVerification", safe(context.education == null ? null : context.education.getEduVerification()));

        // 最近工作单位
        placeholders.put("company", safe(context.experience == null ? null : context.experience.getCompany()));
        placeholders.put("employmentPeriod", safe(resolveEmploymentPeriod(context)));
        placeholders.put("jobTitle", safe(resolveLatestJobTitle(context)));
        placeholders.put("lastSalary", safe(context.experience == null ? null : context.experience.getLastSalary()));
        placeholders.put("leaveReason", safe(context.review == null ? null : context.review.getLeaveReason()));

        // 工作表现评估
        placeholders.put("supervisorEval", safe(context.review == null ? null : context.review.getSupervisorEval()));
        placeholders.put("strength", safe(context.review == null ? null : context.review.getStrength()));
        placeholders.put("improvement", safe(context.review == null ? null : context.review.getImprovement()));
        placeholders.put("professionalAbility", safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityAName(), context.review == null ? null : context.review.getAbilityARate(), context.review == null ? null : context.review.getAbilityARemark())));
        placeholders.put("workAttitude", safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityBName(), context.review == null ? null : context.review.getAbilityBRate(), context.review == null ? null : context.review.getAbilityBRemark())));
        placeholders.put("teamwork", safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityCName(), context.review == null ? null : context.review.getAbilityCRate(), context.review == null ? null : context.review.getAbilityCRemark())));
        placeholders.put("ethics", safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityDName(), context.review == null ? null : context.review.getAbilityDRate(), context.review == null ? null : context.review.getAbilityDRemark())));
        placeholders.put("colleagueEval", safe(context.review == null ? null : context.review.getColleagueEval()));
        placeholders.put("cooperation", safe(context.review == null ? null : context.review.getCooperation()));
        placeholders.put("colleagueAbility", safe(context.review == null ? null : context.review.getColleagueAbility()));
        placeholders.put("hrEval", safe(context.review == null ? null : context.review.getHrEval()));
        placeholders.put("violation", safe(context.review == null ? null : context.review.getViolation()));
        placeholders.put("handover", safe(context.review == null ? null : context.review.getHandover()));

        // 调查结论
        placeholders.put("conclusionReason", safe(context.review == null ? null : context.review.getTotalRemark()));

        // 调查人 / 日期
        placeholders.put("investigator", safe(context.companyName));
        placeholders.put("investigateDate", safe(resolveSurveyTime(context)));

        // 访谈记录 Q&A 填充
        fillInterviewPlaceholders(placeholders, context);

        // 确保所有空值替换为空格，保证模板格式不被破坏
        placeholders.replaceAll((k, v) -> (v == null || v.isEmpty()) ? " " : v);

        // ========== Checkbox 勾选框 ==========
        placeholders.put("CB_eduResult", "属实,不属实,无法核实|SELECTED:" + safe(context.education == null ? null : context.education.getCheckResult()));
        placeholders.put("CB_companyResult", "属实,部分属实,不属实|SELECTED:" + safe(context.experience == null ? null : context.experience.getCheckResult()));
        placeholders.put("CB_performResult", "良好,一般,需关注|SELECTED:" + safe(context.review == null ? null : context.review.getPerformCheckResult()));
        
        String agreeSelect = "";
        if (context.review != null) {
            if (Integer.valueOf(1).equals(context.review.getNonCompeteAgreement())) agreeSelect = "竞业禁止协议：是";
            else if (Integer.valueOf(0).equals(context.review.getNonCompeteAgreement())) agreeSelect = "竞业禁止协议：否";
            
            if (Integer.valueOf(1).equals(context.review.getConfidentialityAgreement())) {
                 agreeSelect += (agreeSelect.isEmpty() ? "" : "|SELECTED:") + "保密协议：是"; // NOT FULLY SUPPORTED by current CB logic to select multiple in one line natively, let's just format it as text if needed, but our WordReportService only supports ONE SELECTED. Wait, our word report service checks if option.equals(selected).
                 // Actually, the placeholder supports one selected. Since they are separate options in the string, if we want multiple, we need to improve WordReportService.
                 // For now, let's map them to 2 CB groups? No, word has them on one line.
            }
        }
        
        // Better approach for restriction agreements: it's better to use two different CB placeholders if they are multiple choices, 
        // but the word template only has one CB_restrictAgreement. Let's fix that dynamically by creating a specialized string or just use the DB value.
        // I will just use text replacing for now or keep it simple. Let's pass the raw string if needed.
        placeholders.put("CB_restrictAgreement", "竞业禁止协议：是,竞业禁止协议：否,保密协议：是,保密协议：否|SELECTED:" + agreeSelect);
        
        String disputeSelect = "";
        if (context.review != null && context.review.getLaborDispute() != null) {
             disputeSelect = context.review.getLaborDispute() == 1 ? "是" : "否";
        }
        placeholders.put("CB_disputeRecord", "是,否|SELECTED:" + disputeSelect);
        placeholders.put("CB_conclusionResult", "推荐入职,有条件推荐,不推荐|SELECTED:" + safe(context.review == null ? null : context.review.getConclusionResult()));

        // ========== 备注列 ==========
        placeholders.put("companyRemark", safe(context.experience == null ? null : context.experience.getCheckRemark()));
        placeholders.put("performRemark", safe(context.review == null ? null : context.review.getPerformRemark()));
        placeholders.put("restrictRemark", safe(context.review == null ? null : context.review.getAgreementRemark()));
        placeholders.put("disputeRemark", safe(context.review == null ? null : context.review.getDisputeRemark()));

        return wordReportService.generatePdf(placeholders);
    }

    private String uploadReportPdf(MainBackRecord record, ReportContext context, byte[] pdfBytes) {
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(pdfBytes)) {
            OssClient ossClient = OssFactory.instance();
            String key = "report/check/" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM"))
                + "/" + record.getId() + "-" + sanitizeFileName(safe(context.student == null ? null : context.student.getName()), "candidate")
                + ".pdf";
            UploadResult uploadResult = ossClient.upload(inputStream, key, (long) pdfBytes.length, "application/pdf");
            return uploadResult.getUrl();
        } catch (Exception e) {
            throw new ServiceException("上传报告PDF失败: " + e.getMessage());
        }
    }

    private String resolvePositionName(ReportContext context) {
        if (context.position != null && StringUtils.isNotBlank(context.position.getPostName())) {
            return context.position.getPostName();
        }
        return resolveLatestJobTitle(context);
    }

    private String resolveLatestJobTitle(ReportContext context) {
        return context.experience == null ? null : context.experience.getJobTitle();
    }

    private String resolveSurveyTime(ReportContext context) {
        if (context.record.getFinishTime() != null) {
            return context.record.getFinishTime().format(REPORT_TIME_FORMATTER);
        }
        if (context.record.getCreateTime() != null) {
            return context.record.getCreateTime().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime()
                .format(REPORT_TIME_FORMATTER);
        }
        return LocalDateTime.now().format(REPORT_TIME_FORMATTER);
    }

    private String resolveSchoolName(ReportContext context) {
        if (context.education != null && StringUtils.isNotBlank(context.education.getSchool())) {
            return context.education.getSchool();
        }
        return context.student == null ? null : context.student.getSchoolName();
    }

    private String resolveEducationVerification(ReportContext context) {
        return "";
    }

    private String resolveEmploymentPeriod(ReportContext context) {
        if (context.review != null && (context.review.getEntryTime() != null || context.review.getLeaveTime() != null)) {
            return joinPeriod(
                context.review.getEntryTime() == null ? null : cn.hutool.core.date.DateUtil.format(context.review.getEntryTime(), "yyyy-MM-dd"),
                context.review.getLeaveTime() == null ? null : cn.hutool.core.date.DateUtil.format(context.review.getLeaveTime(), "yyyy-MM-dd")
            );
        }
        if (context.experience != null) {
            return joinPeriod(context.experience.getStartTime(), context.experience.getEndTime());
        }
        return "";
    }

    private String joinPeriod(String start, String end) {
        if (StringUtils.isBlank(start) && StringUtils.isBlank(end)) {
            return "";
        }
        return safe(start) + " 至 " + safe(end);
    }

    private String resolveEmploymentStatus(ReportContext context) {
        if (context.review == null || StringUtils.isBlank(context.review.getEmploymentStatus())) {
            return "";
        }
        return "left".equalsIgnoreCase(context.review.getEmploymentStatus()) ? "已离职" : "在职";
    }

    private String resolveWorkContent(ReportContext context) {
        return context.experience == null ? null : context.experience.getWorkContent();
    }

    private String resolveKnownAnswer(ReportContext context) {
        List<String> parts = new ArrayList<>();
        if (context.experience != null && StringUtils.isNotBlank(context.experience.getStartTime())) {
            parts.add("可确认最晚于 " + context.experience.getStartTime() + " 已在前司任职");
        }
        if (StringUtils.isNotBlank(resolveLatestJobTitle(context))) {
            parts.add("岗位为" + resolveLatestJobTitle(context));
        }
        if (StringUtils.isNotBlank(resolveWorkContent(context))) {
            parts.add("工作职责包括：" + resolveWorkContent(context));
        }
        return String.join("；", parts);
    }

    private String buildAbilityLine(String name, BigDecimal rate, String remark) {
        if (StringUtils.isBlank(name) && rate == null && StringUtils.isBlank(remark)) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        sb.append(safe(name));
        if (rate != null) {
            sb.append("（").append(rate.stripTrailingZeros().toPlainString()).append("分）");
        }
        if (StringUtils.isNotBlank(remark)) {
            sb.append("：").append(remark);
        }
        return sb.toString();
    }

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

    private String sanitizeFileName(String value, String fallback) {
        String source = StringUtils.isBlank(value) ? fallback : value;
        return source.replaceAll("[\\\\/:*?\"<>|\\s]+", "_");
    }

    /**
     * 填充访谈记录到占位符
     */
    private void fillInterviewPlaceholders(Map<String, String> placeholders, ReportContext context) {
        // 先设默认空值
        for (int i = 1; i <= 3; i++) {
            placeholders.put("interviewee" + i + "Name", " ");
            placeholders.put("interviewee" + i + "Relation", " ");
            placeholders.put("interviewee" + i + "Contact", " ");
            for (int q = 1; q <= 5; q++) {
                placeholders.put("qa" + i + "_a" + q, " ");
            }
        }
        
        if (context.interviews == null || context.interviews.isEmpty()) {
            return;
        }

        // 按顺序填充
        for (int i = 0; i < Math.min(3, context.interviews.size()); i++) {
            MainBackInterview iv = context.interviews.get(i);
            int idx = i + 1;
            placeholders.put("interviewee" + idx + "Name", safe(iv.getIntervieweeName()));
            placeholders.put("interviewee" + idx + "Relation", safe(iv.getIntervieweeRelation()));
            placeholders.put("interviewee" + idx + "Contact", safe(iv.getIntervieweeContact()));
            placeholders.put("qa" + idx + "_a1", safe(iv.getQa1()));
            placeholders.put("qa" + idx + "_a2", safe(iv.getQa2()));
            placeholders.put("qa" + idx + "_a3", safe(iv.getQa3()));
            placeholders.put("qa" + idx + "_a4", safe(iv.getQa4()));
            placeholders.put("qa" + idx + "_a5", safe(iv.getQa5()));
        }
    }

    private static class ReportContext {
        private MainStudent student;
        private MainPostCandidateReview review;
        private MainStudentEducation education;
        private MainStudentExperience experience;
        private List<MainBackInterview> interviews;
        private MainPosition position;
        private MainBackRecord record;
        private MainBackOrder backOrder;
        private String companyName;
    }
}

