package com.ruoyi.jarvis.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.jarvis.domain.GroupRebateExcelUpload;
import com.ruoyi.jarvis.domain.JDOrder;
import com.ruoyi.jarvis.domain.dto.RebateRemarkItem;
import com.ruoyi.jarvis.mapper.GroupRebateExcelUploadMapper;
import com.ruoyi.jarvis.mapper.JDOrderMapper;
import com.ruoyi.jarvis.service.IGroupRebateExcelUploadService;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 解析跟团返现类 Excel（表头含单号、是否返现、返现金额等），写入订单后返备注 JSON；并保存上传文件与数据库记录。
 */
@Service
public class GroupRebateExcelImportService {

    private static final Logger log = LoggerFactory.getLogger(GroupRebateExcelImportService.class);

    private static final DataFormatter DATA_FORMATTER = new DataFormatter();

    private static final String[] EXCEL_EXT = { "xls", "xlsx" };

    @Resource
    private JDOrderMapper jdOrderMapper;

    @Resource
    private GroupRebateExcelUploadMapper groupRebateExcelUploadMapper;

    @Resource
    private IGroupRebateExcelUploadService groupRebateExcelUploadService;

    private GroupRebateExcelImportService self;

    @Autowired
    public void setSelf(@Lazy GroupRebateExcelImportService self) {
        this.self = self;
    }

    /**
     * 先落盘上传文件，再解析写订单；无论成败均写入上传记录（独立事务）。
     */
    public Map<String, Object> importExcel(MultipartFile file, String documentTitle) throws Exception {
        String originalFilename = file != null ? file.getOriginalFilename() : null;
        long fileSize = file != null ? file.getSize() : 0L;
        String title = resolveDocumentTitle(file, documentTitle);
        String webPath = "";

        if (file == null || file.isEmpty()) {
            Map<String, Object> empty = new HashMap<>();
            empty.put("success", false);
            empty.put("message", "请选择文件");
            empty.put("documentTitle", title);
            persistUploadRecord(originalFilename, webPath, fileSize, title, empty);
            return empty;
        }

        try {
            String baseDir = RuoYiConfig.getUploadPath() + File.separator + "group-rebate-excel";
            webPath = FileUploadUtils.upload(baseDir, file, EXCEL_EXT);
        } catch (Exception e) {
            log.warn("保存后返Excel失败", e);
            Map<String, Object> fail = new HashMap<>();
            fail.put("success", false);
            fail.put("message", "保存上传文件失败: " + e.getMessage());
            fail.put("documentTitle", title);
            persistUploadRecord(originalFilename, webPath, fileSize, title, fail);
            return fail;
        }

        File diskFile = resolveDiskFile(webPath);
        Long recordId = insertInitialUploadRecord(originalFilename, webPath, fileSize, title);
        Map<String, Object> result;
        try {
            result = self.processWorkbookTransactional(diskFile, title, recordId);
        } catch (Exception e) {
            log.error("后返表导入处理异常", e);
            result = new HashMap<>();
            result.put("success", false);
            result.put("message", "导入处理异常: " + e.getMessage());
            result.put("documentTitle", title);
        }
        result.put("savedFilePath", webPath);
        finalizeUploadRecord(recordId, originalFilename, webPath, fileSize, title, result);
        result.put("uploadRecordId", recordId);
        return result;
    }

    /**
     * 批量导入：按顺序逐个调用 {@link #importExcel}，在同一请求内由后台依次处理完成。
     *
     * @param documentTitle 仅当恰好 1 个有效文件时作为文档标题；多文件时忽略，各文件用各自文件名。
     */
    public Map<String, Object> importExcelBatch(MultipartFile[] files, String documentTitle) {
        if (files == null || files.length == 0) {
            Map<String, Object> empty = new HashMap<>();
            empty.put("success", false);
            empty.put("message", "请选择文件");
            return empty;
        }
        List<MultipartFile> list = new ArrayList<>();
        for (MultipartFile f : files) {
            if (f != null && !f.isEmpty()) {
                list.add(f);
            }
        }
        if (list.isEmpty()) {
            Map<String, Object> empty = new HashMap<>();
            empty.put("success", false);
            empty.put("message", "没有有效文件");
            return empty;
        }
        boolean single = list.size() == 1;
        String sharedTitle = (single && StringUtils.isNotEmpty(documentTitle)) ? documentTitle.trim() : null;

        int ok = 0;
        int fail = 0;
        List<Map<String, Object>> failures = new ArrayList<>();
        for (MultipartFile file : list) {
            try {
                Map<String, Object> r = importExcel(file, sharedTitle);
                if (Boolean.TRUE.equals(r.get("success"))) {
                    ok++;
                } else {
                    fail++;
                    addBatchFailureBrief(failures, file.getOriginalFilename(), r.get("message"));
                }
            } catch (Exception e) {
                log.warn("批量后返表：单文件导入失败 {}", file.getOriginalFilename(), e);
                fail++;
                addBatchFailureBrief(failures, file.getOriginalFilename(), e.getMessage());
            }
        }
        Map<String, Object> out = new HashMap<>();
        out.put("success", true);
        out.put("totalFiles", list.size());
        out.put("successCount", ok);
        out.put("failCount", fail);
        out.put("message", String.format(Locale.ROOT,
            "已处理 %d 个文件：成功 %d，失败 %d。明细可在「后返上传记录」查看。",
            list.size(), ok, fail));
        if (!failures.isEmpty()) {
            out.put("failures", failures);
        }
        return out;
    }

    private static void addBatchFailureBrief(List<Map<String, Object>> failures, String filename, Object message) {
        if (failures.size() >= 15) {
            return;
        }
        Map<String, Object> fb = new HashMap<>(2);
        fb.put("filename", filename);
        fb.put("message", message != null ? message.toString() : "");
        failures.add(fb);
    }

    /**
     * 删除一条上传记录：从订单 rebate_remark_json 中移除本次导入写入的条目，并删除数据库记录与磁盘文件。
     */
    public Map<String, Object> deleteUploadRecord(Long id) {
        if (id == null) {
            throw new ServiceException("记录 ID 无效");
        }
        GroupRebateExcelUpload rec = groupRebateExcelUploadService.selectGroupRebateExcelUploadById(id);
        if (rec == null) {
            throw new ServiceException("上传记录不存在");
        }
        String filePath = rec.getFilePath();
        Map<String, Object> data = self.deleteUploadRecordTransactional(id, rec);
        deleteUploadDiskFileIfPresent(filePath);
        return data;
    }

    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> deleteUploadRecordTransactional(Long id, GroupRebateExcelUpload rec) {
        Set<Long> orderIds = new LinkedHashSet<>();
        List<Long> fromDetail = parseAffectedOrderIds(rec.getResultDetailJson());
        if (fromDetail != null) {
            orderIds.addAll(fromDetail);
        }
        if (orderIds.isEmpty()) {
            List<Long> fromJson = jdOrderMapper.selectOrderIdsByRebateRemarkUploadRecordId(id);
            if (fromJson != null) {
                orderIds.addAll(fromJson);
            }
        }
        int ordersUpdated = 0;
        for (Long oid : orderIds) {
            if (stripRebateRemarkItemsByUploadRecordId(oid, id)) {
                ordersUpdated++;
            }
        }
        int deleted = groupRebateExcelUploadMapper.deleteGroupRebateExcelUploadById(id);
        if (deleted == 0) {
            throw new ServiceException("删除上传记录失败");
        }
        Map<String, Object> data = new HashMap<>();
        data.put("ordersScanned", orderIds.size());
        data.put("ordersRemarkUpdated", ordersUpdated);
        data.put("revertedByUploadRecordId", id);
        if (orderIds.isEmpty()) {
            data.put("message", "已删除上传记录；未找到可回滚的订单后返备注（多为本功能上线前的历史导入，请按需手工改订单）");
        } else {
            data.put("message", String.format(Locale.ROOT,
                "已删除上传记录；已扫描 %d 个订单，从 %d 个订单移除了本次导入对应的后返备注",
                orderIds.size(), ordersUpdated));
        }
        return data;
    }

    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> processWorkbookTransactional(File diskFile, String title, Long uploadRecordId) throws Exception {
        Map<String, Object> result = new HashMap<>();
        try (InputStream is = new FileInputStream(diskFile); Workbook wb = WorkbookFactory.create(is)) {
            Sheet sheet = wb.getNumberOfSheets() > 0 ? wb.getSheetAt(0) : null;
            if (sheet == null) {
                result.put("success", false);
                result.put("message", "工作簿无工作表");
                result.put("documentTitle", title);
                return result;
            }

            int headerRowIndex = findHeaderRowIndex(sheet);
            if (headerRowIndex < 0) {
                result.put("success", false);
                result.put("message", "未找到表头（需包含「单号」或「订单号」列，以及「是否返现」或「总共返现」相关列）");
                result.put("documentTitle", title);
                return result;
            }

            Row headerRow = sheet.getRow(headerRowIndex);
            HeaderMap hm = buildHeaderMap(headerRow);
            if (hm.orderCol < 0) {
                result.put("success", false);
                result.put("message", "未识别订单号列（表头需含「单号」或「订单号」，不含「第三方」）");
                result.put("documentTitle", title);
                return result;
            }
            if (hm.whetherRebateCol < 0 && hm.totalCashbackCol < 0) {
                result.put("success", false);
                result.put("message", "未识别「是否返现」或返现金额列（如「总共返现」）");
                result.put("documentTitle", title);
                return result;
            }

            if (hm.whetherRebateCol >= 0) {
                int orderDataRows = 0;
                boolean anyWhetherFilled = false;
                for (int r = headerRowIndex + 1; r <= sheet.getLastRowNum(); r++) {
                    Row scanRow = sheet.getRow(r);
                    if (scanRow == null) {
                        continue;
                    }
                    String orderNoScan = normalizeOrderNo(getCellText(scanRow, hm.orderCol));
                    if (orderNoScan.isEmpty()) {
                        continue;
                    }
                    orderDataRows++;
                    String w = trimToNull(getCellText(scanRow, hm.whetherRebateCol));
                    if (w != null) {
                        anyWhetherFilled = true;
                        break;
                    }
                }
                if (orderDataRows > 0 && !anyWhetherFilled) {
                    result.put("success", false);
                    result.put("message", "「是否返现」列全部为空，未写入任何订单，请补全该列后重新上传。");
                    result.put("documentTitle", title);
                    result.put("dataRows", orderDataRows);
                    result.put("updatedOrders", 0);
                    result.put("notFoundOrderNos", new ArrayList<>());
                    result.put("errors", new ArrayList<>());
                    return result;
                }
            }

            int dataRows = 0;
            int updatedOrders = 0;
            Set<Long> affectedOrderIds = new LinkedHashSet<>();
            Set<String> notFound = new LinkedHashSet<>();
            List<String> errors = new ArrayList<>();

            int lastRow = sheet.getLastRowNum();
            for (int r = headerRowIndex + 1; r <= lastRow; r++) {
                Row row = sheet.getRow(r);
                if (row == null) {
                    continue;
                }
                String orderNo = normalizeOrderNo(getCellText(row, hm.orderCol));
                if (orderNo.isEmpty()) {
                    continue;
                }
                dataRows++;

                String whether = hm.whetherRebateCol >= 0 ? trimToNull(getCellText(row, hm.whetherRebateCol)) : "";
                if (whether == null) {
                    whether = "";
                }
                String amountStr = hm.totalCashbackCol >= 0 ? trimToNull(getCellText(row, hm.totalCashbackCol)) : "";
                if ((amountStr == null || amountStr.isEmpty()) && hm.fallbackAmountCol >= 0) {
                    amountStr = trimToNull(getCellText(row, hm.fallbackAmountCol));
                }
                if (amountStr == null) {
                    amountStr = "";
                }

                JDOrder order = jdOrderMapper.selectJDOrderByOrderId(orderNo);
                if (order == null) {
                    order = jdOrderMapper.selectJDOrderByThirdPartyOrderNo(orderNo);
                }
                if (order == null) {
                    notFound.add(orderNo);
                    continue;
                }

                try {
                    appendRebateRemark(order, title, whether, amountStr, uploadRecordId);
                    affectedOrderIds.add(order.getId());
                    updatedOrders++;
                } catch (Exception e) {
                    log.warn("写入后返备注失败 orderNo={} id={}", orderNo, order.getId(), e);
                    errors.add(orderNo + ": " + e.getMessage());
                }
            }

            result.put("success", true);
            result.put("documentTitle", title);
            result.put("dataRows", dataRows);
            result.put("updatedOrders", updatedOrders);
            result.put("notFoundOrderNos", new ArrayList<>(notFound));
            result.put("errors", errors);
            result.put("affectedOrderIds", new ArrayList<>(affectedOrderIds));
            result.put("message", String.format(Locale.ROOT,
                "解析完成：有效数据行 %d，匹配并更新订单 %d，未找到订单 %d 条",
                dataRows, updatedOrders, notFound.size()));
            return result;
        }
    }

    private Long insertInitialUploadRecord(String originalFilename, String webPath, long fileSize, String title) {
        GroupRebateExcelUpload row = new GroupRebateExcelUpload();
        row.setOriginalFilename(originalFilename);
        row.setFilePath(webPath);
        row.setFileSize(fileSize);
        row.setDocumentTitle(title);
        row.setImportStatus(1);
        row.setDataRows(0);
        row.setUpdatedOrders(0);
        row.setNotFoundCount(0);
        JSONObject d = new JSONObject();
        d.put("message", "解析中");
        d.put("documentTitle", title);
        row.setResultDetailJson(d.toJSONString());
        try {
            row.setCreateBy(SecurityUtils.getUsername());
        } catch (Exception e) {
            row.setCreateBy("");
        }
        groupRebateExcelUploadService.saveRecordInNewTransaction(row);
        return row.getId();
    }

    private void finalizeUploadRecord(Long recordId, String originalFilename, String webPath, long fileSize,
                                      String title, Map<String, Object> procResult) {
        GroupRebateExcelUpload row = new GroupRebateExcelUpload();
        row.setId(recordId);
        row.setOriginalFilename(originalFilename);
        row.setFilePath(StringUtils.isNotEmpty(webPath) ? webPath : "");
        row.setFileSize(fileSize);
        row.setDocumentTitle(title);
        applyProcResultToRow(row, procResult);
        try {
            groupRebateExcelUploadService.updateRecordInNewTransaction(row);
        } catch (Exception e) {
            log.error("更新后返上传记录失败 id={}", recordId, e);
        }
    }

    private void applyProcResultToRow(GroupRebateExcelUpload row, Map<String, Object> procResult) {
        boolean ok = Boolean.TRUE.equals(procResult.get("success"));
        row.setImportStatus(ok ? 2 : 1);
        row.setDataRows(asInt(procResult.get("dataRows")));
        row.setUpdatedOrders(asInt(procResult.get("updatedOrders")));
        @SuppressWarnings("unchecked")
        List<String> nf = (List<String>) procResult.get("notFoundOrderNos");
        row.setNotFoundCount(nf != null ? nf.size() : null);

        JSONObject detail = new JSONObject();
        detail.put("message", procResult.get("message"));
        detail.put("documentTitle", procResult.get("documentTitle"));
        if (procResult.containsKey("notFoundOrderNos")) {
            detail.put("notFoundOrderNos", procResult.get("notFoundOrderNos"));
        }
        if (procResult.containsKey("errors")) {
            detail.put("errors", procResult.get("errors"));
        }
        if (procResult.containsKey("savedFilePath")) {
            detail.put("savedFilePath", procResult.get("savedFilePath"));
        }
        if (procResult.containsKey("affectedOrderIds")) {
            detail.put("affectedOrderIds", procResult.get("affectedOrderIds"));
        }
        row.setResultDetailJson(detail.toJSONString());
    }

    private Long persistUploadRecord(String originalFilename, String webPath, long fileSize,
                                     String title, Map<String, Object> procResult) {
        GroupRebateExcelUpload row = new GroupRebateExcelUpload();
        row.setOriginalFilename(originalFilename);
        row.setFilePath(StringUtils.isNotEmpty(webPath) ? webPath : "");
        row.setFileSize(fileSize);
        row.setDocumentTitle(title);
        try {
            row.setCreateBy(SecurityUtils.getUsername());
        } catch (Exception e) {
            row.setCreateBy("");
        }
        applyProcResultToRow(row, procResult);

        try {
            groupRebateExcelUploadService.saveRecordInNewTransaction(row);
            return row.getId();
        } catch (Exception e) {
            log.error("写入后返上传记录失败", e);
            return null;
        }
    }

    private static List<Long> parseAffectedOrderIds(String resultDetailJson) {
        if (StringUtils.isEmpty(resultDetailJson)) {
            return null;
        }
        try {
            JSONObject o = JSON.parseObject(resultDetailJson);
            if (o == null || !o.containsKey("affectedOrderIds")) {
                return null;
            }
            return o.getList("affectedOrderIds", Long.class);
        } catch (Exception e) {
            return null;
        }
    }

    private boolean stripRebateRemarkItemsByUploadRecordId(Long orderId, Long uploadRecordId) {
        JDOrder order = jdOrderMapper.selectJDOrderById(orderId);
        if (order == null) {
            return false;
        }
        List<RebateRemarkItem> list = new ArrayList<>();
        String existing = order.getRebateRemarkJson();
        if (existing != null && !existing.trim().isEmpty()) {
            try {
                list.addAll(JSON.parseArray(existing, RebateRemarkItem.class));
            } catch (Exception e) {
                return false;
            }
        }
        int before = list.size();
        list.removeIf(it -> Objects.equals(uploadRecordId, it.getUploadRecordId()));
        if (list.size() == before) {
            return false;
        }
        boolean hasAbnormal = false;
        for (RebateRemarkItem it : list) {
            if (Boolean.TRUE.equals(it.getAbnormal())) {
                hasAbnormal = true;
                break;
            }
        }
        JDOrder upd = new JDOrder();
        upd.setId(order.getId());
        upd.setRebateRemarkJson(list.isEmpty() ? "[]" : JSON.toJSONString(list));
        upd.setRebateRemarkHasAbnormal(hasAbnormal ? 1 : 0);
        jdOrderMapper.updateJDOrder(upd);
        return true;
    }

    private void deleteUploadDiskFileIfPresent(String webPath) {
        if (StringUtils.isEmpty(webPath)) {
            return;
        }
        try {
            File f = resolveDiskFile(webPath);
            if (f.isFile() && !f.delete()) {
                log.warn("删除后返上传文件失败: {}", f.getAbsolutePath());
            }
        } catch (Exception e) {
            log.warn("删除后返上传文件异常 path={}", webPath, e);
        }
    }

    private static Integer asInt(Object o) {
        if (o == null) {
            return null;
        }
        if (o instanceof Number) {
            return ((Number) o).intValue();
        }
        try {
            return Integer.parseInt(o.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private static String resolveDocumentTitle(MultipartFile file, String documentTitle) {
        String t = documentTitle != null ? documentTitle.trim() : "";
        if (!t.isEmpty()) {
            return t;
        }
        if (file == null) {
            return "未命名文档";
        }
        String name = file.getOriginalFilename();
        if (name != null && name.contains(".")) {
            return name.substring(0, name.lastIndexOf('.'));
        }
        return name != null ? name : "未命名文档";
    }

    /** 将 /profile/... 转为本地 File */
    public static File resolveDiskFile(String webPath) {
        if (StringUtils.isEmpty(webPath)) {
            throw new IllegalArgumentException("file path empty");
        }
        String sub = StringUtils.substringAfter(webPath, Constants.RESOURCE_PREFIX);
        if (sub.startsWith("/")) {
            sub = sub.substring(1);
        }
        return new File(RuoYiConfig.getProfile(), sub);
    }

    private void appendRebateRemark(JDOrder order, String documentTitle, String whetherRebate, String rebateAmount,
                                    Long uploadRecordId) {
        List<RebateRemarkItem> list = new ArrayList<>();
        String existing = order.getRebateRemarkJson();
        if (existing != null && !existing.trim().isEmpty()) {
            try {
                list.addAll(JSON.parseArray(existing, RebateRemarkItem.class));
            } catch (Exception e) {
                log.warn("解析已有 rebate_remark_json 失败 id={}，将覆盖为新数组", order.getId());
                list.clear();
            }
        }

        RebateRemarkItem item = new RebateRemarkItem();
        item.setDocumentTitle(documentTitle);
        item.setWhetherRebate(whetherRebate);
        item.setRebateAmount(rebateAmount);
        item.setUploadTime(System.currentTimeMillis());
        item.setAbnormal(isAbnormalWhetherRebate(whetherRebate));
        item.setUploadRecordId(uploadRecordId);
        list.add(item);

        boolean hasAbnormal = false;
        for (RebateRemarkItem it : list) {
            if (Boolean.TRUE.equals(it.getAbnormal())) {
                hasAbnormal = true;
                break;
            }
        }

        JDOrder upd = new JDOrder();
        upd.setId(order.getId());
        upd.setRebateRemarkJson(JSON.toJSONString(list));
        upd.setRebateRemarkHasAbnormal(hasAbnormal ? 1 : 0);
        jdOrderMapper.updateJDOrder(upd);
    }

    /**
     * 异常项：「是否返现」列经 trim 后不等于「已返现」（含空、其它文案）。
     */
    static boolean isAbnormalWhetherRebate(String text) {
        if (text == null) {
            return true;
        }
        return !"已返现".equals(text.trim());
    }

    private static int findHeaderRowIndex(Sheet sheet) {
        int maxScan = Math.min(15, sheet.getLastRowNum() + 1);
        for (int i = 0; i < maxScan; i++) {
            Row row = sheet.getRow(i);
            if (row == null) {
                continue;
            }
            boolean hasOrder = false;
            boolean hasWhether = false;
            boolean hasAmount = false;
            for (int c = 0; c <= row.getLastCellNum() && c < 64; c++) {
                String h = normalizeHeader(getCellText(row, c));
                if (h.isEmpty()) {
                    continue;
                }
                if (isOrderHeader(h)) {
                    hasOrder = true;
                }
                if (h.contains("是否返现")) {
                    hasWhether = true;
                }
                if (h.contains("总共返现") || (h.contains("返现") && h.contains("金额"))) {
                    hasAmount = true;
                }
            }
            if (hasOrder && (hasWhether || hasAmount)) {
                return i;
            }
        }
        return -1;
    }

    private static boolean isOrderHeader(String h) {
        if (h.contains("第三方")) {
            return false;
        }
        if ("单号".equals(h) || h.startsWith("单号")) {
            return true;
        }
        return h.contains("订单号");
    }

    private static HeaderMap buildHeaderMap(Row headerRow) {
        HeaderMap hm = new HeaderMap();
        Map<String, Integer> totalCashbackCandidates = new LinkedHashMap<>();
        for (int c = 0; c <= headerRow.getLastCellNum() && c < 128; c++) {
            String raw = normalizeHeader(getCellText(headerRow, c));
            if (raw.isEmpty()) {
                continue;
            }
            if (hm.orderCol < 0 && isOrderHeader(raw)) {
                hm.orderCol = c;
            }
            if (hm.whetherRebateCol < 0 && raw.contains("是否返现")) {
                hm.whetherRebateCol = c;
            }
            if (raw.contains("总共返现")) {
                totalCashbackCandidates.put(raw, c);
            }
            if (hm.fallbackAmountCol < 0 && raw.contains("返现") && raw.contains("金额")
                && !raw.contains("晒单") && hm.totalCashbackCol < 0) {
                hm.fallbackAmountCol = c;
            }
        }
        if (!totalCashbackCandidates.isEmpty()) {
            hm.totalCashbackCol = totalCashbackCandidates.values().iterator().next();
        }
        return hm;
    }

    private static String normalizeHeader(String s) {
        if (s == null) {
            return "";
        }
        return s.replace('\n', ' ').replace('\r', ' ').trim();
    }

    private static String getCellText(Row row, int col) {
        if (row == null || col < 0) {
            return "";
        }
        Cell cell = row.getCell(col);
        if (cell == null) {
            return "";
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    return DATA_FORMATTER.formatCellValue(cell);
                }
                double d = cell.getNumericCellValue();
                if (d == Math.floor(d) && Math.abs(d) < 1e16) {
                    return String.valueOf((long) d);
                }
                return DATA_FORMATTER.formatCellValue(cell);
            case BOOLEAN:
                return Boolean.toString(cell.getBooleanCellValue());
            case FORMULA:
                try {
                    return DATA_FORMATTER.formatCellValue(cell);
                } catch (Exception e) {
                    return "";
                }
            default:
                return DATA_FORMATTER.formatCellValue(cell);
        }
    }

    private static String trimToNull(String s) {
        if (s == null) {
            return null;
        }
        String t = s.trim();
        return t.isEmpty() ? null : t;
    }

    private static String normalizeOrderNo(String raw) {
        if (raw == null) {
            return "";
        }
        String t = raw.trim();
        if (t.endsWith(".0") && t.matches("\\d+\\.0")) {
            t = t.substring(0, t.length() - 2);
        }
        return t;
    }

    private static class HeaderMap {
        int orderCol = -1;
        int whetherRebateCol = -1;
        int totalCashbackCol = -1;
        int fallbackAmountCol = -1;
    }
}
