package com.ruoyi.info.collection.service.impl;

import com.alibaba.fastjson2.JSON;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransaction;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransactionSupplier;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.PurchaseTransactionImportFailureVO;
import com.ruoyi.info.collection.enums.DataSource;
import com.ruoyi.info.collection.enums.EnterpriseSource;
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper;
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionSupplierMapper;
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService;
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 采购交易信息异步导入服务层处理
 *
 * @author ruoyi
 * @date 2026-02-06
 */
@Service
@EnableAsync
public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTransactionImportService {

    private static final Logger log = LoggerFactory.getLogger(CcdiPurchaseTransactionImportServiceImpl.class);
    private static final String MAIN_SHEET_NAME = "招投标主信息";
    private static final String SUPPLIER_SHEET_NAME = "供应商明细";
    private static final int EXCEL_DATA_START_ROW = 2;

    @Resource
    private CcdiPurchaseTransactionMapper transactionMapper;

    @Resource
    private CcdiPurchaseTransactionSupplierMapper supplierMapper;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private EnterpriseAutoFillService enterpriseAutoFillService;

    @Override
    @Async
    @Transactional
    public void importTransactionAsync(
        List<CcdiPurchaseTransactionExcel> mainExcelList,
        List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList,
        String taskId,
        String userName
    ) {
        long startTime = System.currentTimeMillis();
        List<CcdiPurchaseTransactionExcel> safeMainList = mainExcelList == null ? List.of() : mainExcelList;
        List<CcdiPurchaseTransactionSupplierExcel> safeSupplierList = supplierExcelList == null ? List.of() : supplierExcelList;
        List<MainImportRow> indexedMainRows = buildMainImportRows(safeMainList);
        List<SupplierImportRow> indexedSupplierRows = buildSupplierImportRows(safeSupplierList);
        int totalCount = countImportUnits(safeMainList, safeSupplierList);

        // 记录导入开始
        ImportLogUtils.logImportStart(log, taskId, "招投标信息维护", totalCount, userName);

        List<CcdiPurchaseTransaction> newTransactions = new ArrayList<>();
        List<CcdiPurchaseTransactionSupplier> newSuppliers = new ArrayList<>();
        List<PurchaseTransactionImportFailureVO> failures = new ArrayList<>();

        // 批量查询已存在的采购事项ID
        ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的采购事项ID", safeMainList.size());
        Set<String> existingIds = getExistingPurchaseIds(safeMainList);
        ImportLogUtils.logBatchQueryComplete(log, taskId, "采购事项ID", existingIds.size());

        Map<String, List<MainImportRow>> mainGroupMap = indexedMainRows.stream()
            .filter(item -> StringUtils.isNotEmpty(item.data().getPurchaseId()))
            .collect(Collectors.groupingBy(
                item -> item.data().getPurchaseId(),
                LinkedHashMap::new,
                Collectors.toList()
            ));
        Map<String, List<SupplierImportRow>> supplierGroupMap = indexedSupplierRows.stream()
            .filter(item -> StringUtils.isNotEmpty(item.data().getPurchaseId()))
            .collect(Collectors.groupingBy(
                item -> item.data().getPurchaseId(),
                LinkedHashMap::new,
                Collectors.toList()
            ));
        LinkedHashSet<String> purchaseIds = new LinkedHashSet<>();
        purchaseIds.addAll(mainGroupMap.keySet());
        purchaseIds.addAll(supplierGroupMap.keySet());

        for (SupplierImportRow supplierExcel : indexedSupplierRows) {
            if (StringUtils.isEmpty(supplierExcel.data().getPurchaseId())) {
                failures.add(buildFailure(
                    null,
                    null,
                    SUPPLIER_SHEET_NAME,
                    String.valueOf(supplierExcel.sheetRowNum()),
                    "供应商明细Sheet中的采购事项ID不能为空"
                ));
            }
        }

        int index = 0;
        for (String purchaseId : purchaseIds) {
            index++;
            List<MainImportRow> mainRows = mainGroupMap.getOrDefault(purchaseId, List.of());
            List<SupplierImportRow> supplierRows = supplierGroupMap.getOrDefault(purchaseId, List.of());

            try {
                if (existingIds.contains(purchaseId)) {
                    throw buildValidationException(
                        MAIN_SHEET_NAME,
                        extractMainRowNums(mainRows),
                        String.format("采购事项ID[%s]已存在，请勿重复导入", purchaseId)
                    );
                }
                if (mainRows.isEmpty()) {
                    throw buildValidationException(
                        SUPPLIER_SHEET_NAME,
                        extractSupplierRowNums(supplierRows),
                        String.format("采购事项ID[%s]缺少招投标主信息", purchaseId)
                    );
                }
                if (mainRows.size() > 1) {
                    throw buildValidationException(
                        MAIN_SHEET_NAME,
                        extractMainRowNums(mainRows),
                        String.format("采购事项ID[%s]在招投标主信息Sheet中重复", purchaseId)
                    );
                }

                MainImportRow mainRow = mainRows.getFirst();
                CcdiPurchaseTransactionExcel mainExcel = mainRow.data();
                CcdiPurchaseTransactionAddDTO addDTO = new CcdiPurchaseTransactionAddDTO();
                BeanUtils.copyProperties(mainExcel, addDTO);

                validateTransactionData(addDTO, mainRow.sheetRowNum());
                List<CcdiPurchaseTransactionSupplier> suppliers = buildSupplierEntities(purchaseId, supplierRows, userName);

                CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
                BeanUtils.copyProperties(mainExcel, transaction);
                fillWinnerSummary(transaction, suppliers);
                transaction.setCreatedBy(userName);
                transaction.setUpdatedBy(userName);
                newTransactions.add(transaction);
                newSuppliers.addAll(suppliers);

                // 记录进度
                ImportLogUtils.logProgress(log, taskId, index, Math.max(totalCount, purchaseIds.size()),
                    newTransactions.size(), failures.size());

            } catch (Exception e) {
                MainImportRow mainRow = mainRows.isEmpty() ? null : mainRows.getFirst();
                CcdiPurchaseTransactionExcel mainExcel = mainRow == null ? null : mainRow.data();
                FailureMeta failureMeta = resolveFailureMeta(e, mainRows, supplierRows);
                failures.add(buildFailure(
                    mainExcel,
                    purchaseId,
                    failureMeta.sheetName(),
                    failureMeta.sheetRowNum(),
                    e.getMessage()
                ));

                // 记录验证失败日志
                String keyData = String.format("采购事项ID=%s, 采购类别=%s, 标的物=%s",
                    purchaseId,
                    mainExcel == null ? "" : mainExcel.getPurchaseCategory(),
                    mainExcel == null ? "" : mainExcel.getSubjectName());
                ImportLogUtils.logValidationError(log, taskId, index, e.getMessage(), keyData);
            }
        }

        // 批量插入新数据
        if (!newTransactions.isEmpty()) {
            autoFillSupplierEnterprises(newSuppliers, userName);
            ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
                (newTransactions.size() + 499) / 500, 500);
            saveBatch(newTransactions, 500);
        }
        if (!newSuppliers.isEmpty()) {
            saveSupplierBatch(newSuppliers, 500);
        }

        // 保存失败记录到Redis
        if (!failures.isEmpty()) {
            try {
                String failuresKey = "import:purchaseTransaction:" + taskId + ":failures";
                redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
                ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
            } catch (Exception e) {
                ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
            }
        }

        ImportResult result = new ImportResult();
        result.setTotalCount(totalCount);
        result.setSuccessCount(newTransactions.size());
        result.setFailureCount(failures.size());

        // 更新最终状态
        String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
        updateImportStatus(taskId, finalStatus, result);

        // 记录导入完成
        long duration = System.currentTimeMillis() - startTime;
        ImportLogUtils.logImportComplete(log, taskId, "招投标信息维护",
            totalCount, result.getSuccessCount(), result.getFailureCount(), duration);
    }

    /**
     * 获取导入失败记录
     *
     * @param taskId 任务ID
     * @return 失败记录列表
     */
    @Override
    public List<PurchaseTransactionImportFailureVO> getImportFailures(String taskId) {
        String key = "import:purchaseTransaction:" + taskId + ":failures";
        Object failuresObj = redisTemplate.opsForValue().get(key);

        if (failuresObj == null) {
            return Collections.emptyList();
        }

        return JSON.parseArray(JSON.toJSONString(failuresObj), PurchaseTransactionImportFailureVO.class);
    }

    /**
     * 查询导入状态
     *
     * @param taskId 任务ID
     * @return 导入状态信息
     */
    @Override
    public ImportStatusVO getImportStatus(String taskId) {
        String key = "import:purchaseTransaction:" + taskId;
        Boolean hasKey = redisTemplate.hasKey(key);

        if (Boolean.FALSE.equals(hasKey)) {
            throw new RuntimeException("任务不存在或已过期");
        }

        Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);

        ImportStatusVO statusVO = new ImportStatusVO();
        statusVO.setTaskId((String) statusMap.get("taskId"));
        statusVO.setStatus((String) statusMap.get("status"));
        statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
        statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
        statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
        statusVO.setProgress((Integer) statusMap.get("progress"));
        statusVO.setStartTime((Long) statusMap.get("startTime"));
        statusVO.setEndTime((Long) statusMap.get("endTime"));
        statusVO.setMessage((String) statusMap.get("message"));

        return statusVO;
    }

    /**
     * 更新导入状态
     */
    private void updateImportStatus(String taskId, String status, ImportResult result) {
        String key = "import:purchaseTransaction:" + taskId;
        Map<String, Object> statusData = new HashMap<>();
        statusData.put("status", status);
        statusData.put("successCount", result.getSuccessCount());
        statusData.put("failureCount", result.getFailureCount());
        statusData.put("progress", 100);
        statusData.put("endTime", System.currentTimeMillis());

        if ("SUCCESS".equals(status)) {
            statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
        } else {
            statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条");
        }

        redisTemplate.opsForHash().putAll(key, statusData);
    }

    /**
     * 批量查询已存在的采购事项ID
     */
    private Set<String> getExistingPurchaseIds(List<CcdiPurchaseTransactionExcel> excelList) {
        List<String> purchaseIds = excelList.stream()
                .map(CcdiPurchaseTransactionExcel::getPurchaseId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        if (purchaseIds.isEmpty()) {
            return Collections.emptySet();
        }

        List<CcdiPurchaseTransaction> existingTransactions = transactionMapper.selectBatchIds(purchaseIds);
        return existingTransactions.stream()
                .map(CcdiPurchaseTransaction::getPurchaseId)
                .collect(Collectors.toSet());
    }

    /**
     * 批量保存
     */
    private void saveBatch(List<CcdiPurchaseTransaction> list, int batchSize) {
        // 使用真正的批量插入,分批次执行以提高性能
        for (int i = 0; i < list.size(); i += batchSize) {
            int end = Math.min(i + batchSize, list.size());
            List<CcdiPurchaseTransaction> subList = list.subList(i, end);
            transactionMapper.insertBatch(subList);
        }
    }

    private void saveSupplierBatch(List<CcdiPurchaseTransactionSupplier> list, int batchSize) {
        for (int i = 0; i < list.size(); i += batchSize) {
            int end = Math.min(i + batchSize, list.size());
            List<CcdiPurchaseTransactionSupplier> subList = list.subList(i, end);
            for (CcdiPurchaseTransactionSupplier supplier : subList) {
                supplierMapper.insert(supplier);
            }
        }
    }

    private void autoFillSupplierEnterprises(List<CcdiPurchaseTransactionSupplier> supplierList, String userName) {
        enterpriseAutoFillService.ensureExistsBatch(supplierList.stream()
            .filter(item -> StringUtils.isNotEmpty(item.getSupplierUscc()))
            .map(item -> new EnterpriseAutoFillService.EnterpriseFillItem(
                item.getSupplierUscc(),
                item.getSupplierName(),
                EnterpriseSource.SUPPLIER.getCode(),
                DataSource.IMPORT.getCode(),
                userName
            ))
            .toList());
    }

    /**
     * 验证采购交易数据
     *
     * @param addDTO 新增DTO
     */
    private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO, int sheetRowNum) {
        // 验证必填字段
        if (StringUtils.isEmpty(addDTO.getPurchaseId())) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购事项ID不能为空");
        }
        if (StringUtils.isEmpty(addDTO.getPurchaseCategory())) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购类别不能为空");
        }
        if (StringUtils.isEmpty(addDTO.getSubjectName())) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "标的物名称不能为空");
        }
        if (addDTO.getPurchaseQty() == null) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购数量不能为空");
        }
        if (addDTO.getBudgetAmount() == null) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "预算金额不能为空");
        }
        if (StringUtils.isEmpty(addDTO.getPurchaseMethod())) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购方式不能为空");
        }
        if (addDTO.getApplyDate() == null) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购申请日期不能为空");
        }
        if (StringUtils.isEmpty(addDTO.getApplicantId())) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请人工号不能为空");
        }
        if (StringUtils.isEmpty(addDTO.getApplicantName())) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请人姓名不能为空");
        }
        if (StringUtils.isEmpty(addDTO.getApplyDepartment())) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请部门不能为空");
        }

        // 验证工号格式（7位数字）
        if (!addDTO.getApplicantId().matches("^\\d{7}$")) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请人工号必须为7位数字");
        }
        if (StringUtils.isNotEmpty(addDTO.getPurchaseLeaderId()) && !addDTO.getPurchaseLeaderId().matches("^\\d{7}$")) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购负责人工号必须为7位数字");
        }

        // 验证金额非负
        if (addDTO.getPurchaseQty().compareTo(BigDecimal.ZERO) <= 0) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购数量必须大于0");
        }
        if (addDTO.getBudgetAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "预算金额必须大于0");
        }
        if (addDTO.getBidAmount() != null && addDTO.getBidAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "中标金额必须大于0");
        }
        if (addDTO.getActualAmount() != null && addDTO.getActualAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "实际采购金额必须大于0");
        }
        if (addDTO.getContractAmount() != null && addDTO.getContractAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "合同金额必须大于0");
        }
        if (addDTO.getSettlementAmount() != null && addDTO.getSettlementAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "结算金额必须大于0");
        }
    }

    private List<CcdiPurchaseTransactionSupplier> buildSupplierEntities(
        String purchaseId,
        List<SupplierImportRow> supplierRows,
        String userName
    ) {
        List<SupplierImportRow> normalizedRows = supplierRows == null
            ? List.of()
            : supplierRows.stream()
                .filter(Objects::nonNull)
                .filter(item -> hasAnySupplierValue(item.data()))
                .toList();

        List<Integer> winnerRowNums = new ArrayList<>();
        Map<String, Integer> supplierKeyRowMap = new LinkedHashMap<>();
        List<CcdiPurchaseTransactionSupplier> result = new ArrayList<>();
        for (SupplierImportRow supplierImportRow : normalizedRows) {
            CcdiPurchaseTransactionSupplierExcel supplierRow = supplierImportRow.data();
            int isBidWinner = validateSupplierRow(supplierImportRow);
            if (isBidWinner == 1) {
                winnerRowNums.add(supplierImportRow.sheetRowNum());
            }

            String duplicateKey = StringUtils.trimToEmpty(supplierRow.getSupplierName()) + "|"
                + StringUtils.trimToEmpty(supplierRow.getSupplierUscc());
            Integer firstRowNum = supplierKeyRowMap.putIfAbsent(duplicateKey, supplierImportRow.sheetRowNum());
            if (firstRowNum != null) {
                throw buildValidationException(
                    SUPPLIER_SHEET_NAME,
                    List.of(firstRowNum, supplierImportRow.sheetRowNum()),
                    String.format("采购事项ID[%s]存在重复供应商", purchaseId)
                );
            }

            CcdiPurchaseTransactionSupplier supplier = new CcdiPurchaseTransactionSupplier();
            supplier.setPurchaseId(purchaseId);
            supplier.setSupplierName(StringUtils.trim(supplierRow.getSupplierName()));
            supplier.setSupplierUscc(StringUtils.trimToNull(supplierRow.getSupplierUscc()));
            supplier.setContactPerson(StringUtils.trimToNull(supplierRow.getContactPerson()));
            supplier.setContactPhone(StringUtils.trimToNull(supplierRow.getContactPhone()));
            supplier.setSupplierBankAccount(StringUtils.trimToNull(supplierRow.getSupplierBankAccount()));
            supplier.setIsBidWinner(isBidWinner);
            supplier.setSortOrder(supplierRow.getSortOrder() == null ? result.size() + 1 : supplierRow.getSortOrder());
            supplier.setCreatedBy(userName);
            supplier.setUpdatedBy(userName);
            result.add(supplier);
        }
        if (winnerRowNums.size() > 1) {
            throw buildValidationException(
                SUPPLIER_SHEET_NAME,
                winnerRowNums,
                String.format("采购事项ID[%s]存在多条中标供应商", purchaseId)
            );
        }
        return result;
    }

    private int validateSupplierRow(SupplierImportRow supplierImportRow) {
        CcdiPurchaseTransactionSupplierExcel supplierRow = supplierImportRow.data();
        if (StringUtils.isEmpty(supplierRow.getSupplierName())) {
            throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商名称不能为空");
        }
        if (StringUtils.length(supplierRow.getSupplierName()) > 200) {
            throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商名称长度不能超过200个字符");
        }
        if (StringUtils.length(supplierRow.getContactPerson()) > 50) {
            throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商联系人长度不能超过50个字符");
        }
        if (StringUtils.length(supplierRow.getSupplierBankAccount()) > 50) {
            throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商银行账户长度不能超过50个字符");
        }
        if (StringUtils.isNotEmpty(supplierRow.getContactPhone())
            && !supplierRow.getContactPhone().matches("^1[3-9]\\d{9}$|^0\\d{2,3}-?\\d{7,8}$")) {
            throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商联系电话格式不正确");
        }
        if (StringUtils.isNotEmpty(supplierRow.getSupplierUscc())
            && !supplierRow.getSupplierUscc().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) {
            throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商统一信用代码格式不正确");
        }
        return parseIsBidWinner(supplierRow.getIsBidWinner(), supplierImportRow.sheetRowNum());
    }

    private boolean hasAnySupplierValue(CcdiPurchaseTransactionSupplierExcel supplierRow) {
        return StringUtils.isNotEmpty(supplierRow.getPurchaseId())
            || StringUtils.isNotEmpty(supplierRow.getSupplierName())
            || StringUtils.isNotEmpty(supplierRow.getSupplierUscc())
            || StringUtils.isNotEmpty(supplierRow.getContactPerson())
            || StringUtils.isNotEmpty(supplierRow.getContactPhone())
            || StringUtils.isNotEmpty(supplierRow.getSupplierBankAccount())
            || StringUtils.isNotEmpty(supplierRow.getIsBidWinner())
            || supplierRow.getSortOrder() != null;
    }

    private int parseIsBidWinner(String rawValue, Integer sheetRowNum) {
        if (StringUtils.isEmpty(rawValue)) {
            return 0;
        }
        String normalized = StringUtils.trim(rawValue);
        if ("1".equals(normalized) || "是".equals(normalized) || "Y".equalsIgnoreCase(normalized)
            || "TRUE".equalsIgnoreCase(normalized)) {
            return 1;
        }
        if ("0".equals(normalized) || "否".equals(normalized) || "N".equalsIgnoreCase(normalized)
            || "FALSE".equalsIgnoreCase(normalized)) {
            return 0;
        }
        throw buildValidationException(
            SUPPLIER_SHEET_NAME,
            sheetRowNum == null ? List.of() : List.of(sheetRowNum),
            "是否中标仅支持填写“是/否”或“1/0”"
        );
    }

    private void fillWinnerSummary(
        CcdiPurchaseTransaction transaction,
        List<CcdiPurchaseTransactionSupplier> supplierList
    ) {
        CcdiPurchaseTransactionSupplier winner = supplierList.stream()
            .filter(item -> Objects.equals(item.getIsBidWinner(), 1))
            .findFirst()
            .orElse(null);
        if (winner == null) {
            transaction.setSupplierName(null);
            transaction.setSupplierUscc(null);
            transaction.setContactPerson(null);
            transaction.setContactPhone(null);
            transaction.setSupplierBankAccount(null);
            return;
        }
        transaction.setSupplierName(winner.getSupplierName());
        transaction.setSupplierUscc(winner.getSupplierUscc());
        transaction.setContactPerson(winner.getContactPerson());
        transaction.setContactPhone(winner.getContactPhone());
        transaction.setSupplierBankAccount(winner.getSupplierBankAccount());
    }

    private PurchaseTransactionImportFailureVO buildFailure(
        CcdiPurchaseTransactionExcel mainExcel,
        String purchaseId,
        String sheetName,
        String sheetRowNum,
        String errorMessage
    ) {
        PurchaseTransactionImportFailureVO failure = new PurchaseTransactionImportFailureVO();
        if (mainExcel != null) {
            BeanUtils.copyProperties(mainExcel, failure);
        }
        failure.setSheetName(sheetName);
        failure.setSheetRowNum(sheetRowNum);
        if (StringUtils.isNotEmpty(purchaseId)) {
            failure.setPurchaseId(purchaseId);
        }
        failure.setErrorMessage(errorMessage);
        return failure;
    }

    private int countImportUnits(
        List<CcdiPurchaseTransactionExcel> mainExcelList,
        List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
    ) {
        LinkedHashSet<String> purchaseIds = new LinkedHashSet<>();
        purchaseIds.addAll(
            mainExcelList.stream()
                .map(CcdiPurchaseTransactionExcel::getPurchaseId)
                .filter(StringUtils::isNotEmpty)
                .collect(Collectors.toCollection(LinkedHashSet::new))
        );
        purchaseIds.addAll(
            supplierExcelList.stream()
                .map(CcdiPurchaseTransactionSupplierExcel::getPurchaseId)
                .filter(StringUtils::isNotEmpty)
                .collect(Collectors.toCollection(LinkedHashSet::new))
        );
        return purchaseIds.size();
    }

    private List<MainImportRow> buildMainImportRows(List<CcdiPurchaseTransactionExcel> mainExcelList) {
        List<MainImportRow> rows = new ArrayList<>();
        for (int i = 0; i < mainExcelList.size(); i++) {
            rows.add(new MainImportRow(mainExcelList.get(i), i + EXCEL_DATA_START_ROW));
        }
        return rows;
    }

    private List<SupplierImportRow> buildSupplierImportRows(List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList) {
        List<SupplierImportRow> rows = new ArrayList<>();
        for (int i = 0; i < supplierExcelList.size(); i++) {
            rows.add(new SupplierImportRow(supplierExcelList.get(i), i + EXCEL_DATA_START_ROW));
        }
        return rows;
    }

    private List<Integer> extractMainRowNums(List<MainImportRow> rows) {
        return rows.stream().map(MainImportRow::sheetRowNum).toList();
    }

    private List<Integer> extractSupplierRowNums(List<SupplierImportRow> rows) {
        return rows.stream().map(SupplierImportRow::sheetRowNum).toList();
    }

    private ImportValidationException buildValidationException(String sheetName, List<Integer> rowNums, String message) {
        return new ImportValidationException(sheetName, formatSheetRowNum(rowNums), message);
    }

    private FailureMeta resolveFailureMeta(
        Exception exception,
        List<MainImportRow> mainRows,
        List<SupplierImportRow> supplierRows
    ) {
        if (exception instanceof ImportValidationException validationException) {
            return new FailureMeta(validationException.getSheetName(), validationException.getSheetRowNum());
        }
        if (!mainRows.isEmpty()) {
            return new FailureMeta(MAIN_SHEET_NAME, formatSheetRowNum(extractMainRowNums(mainRows)));
        }
        if (!supplierRows.isEmpty()) {
            return new FailureMeta(SUPPLIER_SHEET_NAME, formatSheetRowNum(extractSupplierRowNums(supplierRows)));
        }
        return new FailureMeta("", "");
    }

    private String formatSheetRowNum(List<Integer> rowNums) {
        if (rowNums == null || rowNums.isEmpty()) {
            return "";
        }
        return rowNums.stream()
            .filter(Objects::nonNull)
            .distinct()
            .sorted()
            .map(String::valueOf)
            .collect(Collectors.joining("、"));
    }

    private record MainImportRow(CcdiPurchaseTransactionExcel data, int sheetRowNum) {}

    private record SupplierImportRow(CcdiPurchaseTransactionSupplierExcel data, int sheetRowNum) {}

    private record FailureMeta(String sheetName, String sheetRowNum) {}

    private static class ImportValidationException extends RuntimeException {

        private final String sheetName;
        private final String sheetRowNum;

        private ImportValidationException(String sheetName, String sheetRowNum, String message) {
            super(message);
            this.sheetName = sheetName;
            this.sheetRowNum = sheetRowNum;
        }

        public String getSheetName() {
            return sheetName;
        }

        public String getSheetRowNum() {
            return sheetRowNum;
        }
    }
}
