/**
 * @ProjectName: 智慧评标-主观分助手
 * @Package: com.zhpb.gdyd_zhpb_zgf.service.impl
 * @ClassName: PdfReadServiceImpl
 * @Description: PDF文件读取服务实现类，使用Apache PDFBox异步读取PDF文件内容并输出到控制台
 * @Author: 张志锋
 * @Date: 2025-10-31
 * @Version: 1.0
 * @Copyright: © 2025 智慧评标-主观分助手
 */
package com.zhpb.gdyd_zhpb_zgf.service.impl;

import com.zhpb.gdyd_zhpb_zgf.entity.mysql.FileType;
import com.zhpb.gdyd_zhpb_zgf.entity.mysql.Supplier;
import com.zhpb.gdyd_zhpb_zgf.entity.pgsql.PdfVector;
import com.zhpb.gdyd_zhpb_zgf.repository.mysql.FileRepository;
import com.zhpb.gdyd_zhpb_zgf.repository.mysql.FileTypeRepository;
import com.zhpb.gdyd_zhpb_zgf.repository.mysql.SupplierRepository;
import com.zhpb.gdyd_zhpb_zgf.repository.pgsql.PdfVectorRepository;
import com.zhpb.gdyd_zhpb_zgf.service.FileTypeService;
import com.zhpb.gdyd_zhpb_zgf.service.FilePathResolver;
import com.zhpb.gdyd_zhpb_zgf.service.PdfReadService;
import com.zhpb.gdyd_zhpb_zgf.service.VectorizationService;
import com.zhpb.gdyd_zhpb_zgf.utils.FilePathOrganizer;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

/**
 * PDF文件读取服务实现类
 */
@Service
public class PdfReadServiceImpl implements PdfReadService {

    private static final Logger logger = LoggerFactory.getLogger(PdfReadServiceImpl.class);

    @Autowired
    private PdfVectorRepository pdfVectorRepository;

    @Autowired
    private FileRepository fileRepository;

    @Autowired
    private SupplierRepository supplierRepository;

    @Autowired
    private FileTypeRepository fileTypeRepository;

    @Autowired
    private FileTypeService fileTypeService;

    @Autowired
    private FilePathResolver filePathResolver;

    @Autowired
    private VectorizationService vectorizationService;

    @Autowired
    private EntityManagerFactory entityManagerFactory;
    
    private EntityManager getEntityManager() {
        return entityManagerFactory.createEntityManager();
    }

    @Value("${vectorize.base-url:http://localhost:8001}")
    private String vectorizeBaseUrl;

    @Value("${vectorize.api-path:/vectorize}")
    private String vectorizeApiPath;

    @Value("${vectorize.dimension:896}")
    private Integer defaultDimension;

    @Value("${vectorize.concurrency:2}")
    private int concurrency;

    private java.util.concurrent.ExecutorService executorService;

    @PostConstruct
    public void init() {
        // 确保并发数至少为1
        int nThreads = Math.max(1, concurrency);
        logger.info("初始化向量化线程池，并发数: {}", nThreads);
        this.executorService = java.util.concurrent.Executors.newFixedThreadPool(nThreads);
    }

    @PreDestroy
    public void destroy() {
        if (executorService != null) {
            executorService.shutdown();
        }
    }

    @Override
    @Async
    public void readPdfFilesAsync(List<String> pdfFilePaths, String projectId) {
        long startTime = System.currentTimeMillis();
        logger.info("[文本解析] ========== 开始处理PDF文件，共 {} 个，项目ID: {} ==========", pdfFilePaths.size(), projectId);
        
        // 更新文件向量化状态为处理中
        updateFilesVectorizationStatus(pdfFilePaths, "processing");
        
        // 使用原子计数器确保线程安全
        java.util.concurrent.atomic.AtomicInteger successCount = new java.util.concurrent.atomic.AtomicInteger(0);
        java.util.concurrent.atomic.AtomicInteger failCount = new java.util.concurrent.atomic.AtomicInteger(0);
        java.util.concurrent.atomic.AtomicInteger vectorizeSuccessCount = new java.util.concurrent.atomic.AtomicInteger(0);
        java.util.concurrent.atomic.AtomicInteger vectorizeFailCount = new java.util.concurrent.atomic.AtomicInteger(0);
        
        List<java.util.concurrent.CompletableFuture<Void>> futures = new java.util.ArrayList<>();

        for (String pdfFilePath : pdfFilePaths) {
            java.util.concurrent.CompletableFuture<Void> future = java.util.concurrent.CompletableFuture.runAsync(() -> {
                try {
                    Path path = Paths.get(pdfFilePath);
                    
                    // 检查文件是否存在
                    if (!Files.exists(path)) {
                        logger.warn("PDF文件不存在: {}", pdfFilePath);
                        failCount.incrementAndGet();
                        return;
                    }
                    
                    // 检查文件扩展名
                    String fileName = path.getFileName().toString();
                    if (!fileName.toLowerCase().endsWith(".pdf")) {
                        logger.debug("跳过非PDF文件: {}", pdfFilePath);
                        return;
                    }
                    
                    // 读取PDF内容
                    String content = readPdfContent(path);
                    
                    // 表格提取功能已禁用（消耗大量时间）
                    // 如需启用，取消以下注释：
                    // try {
                    //     content = com.zhpb.gdyd_zhpb_zgf.utils.PdfTableExtractor.enhanceTextWithTables(pdfFilePath, content);
                    //     logger.debug("PDF表格增强完成: {}", fileName);
                    // } catch (Exception e) {
                    //     logger.warn("PDF表格提取失败，使用原始文本: {}", e.getMessage());
                    // }
                    
                    // 输出到控制台（使用System.out和logger双重输出）
                    // 为了避免多线程控制台输出混乱，使用同步块
                    synchronized (System.out) {
                        System.out.println("\n" + "=".repeat(80));
                        System.out.println("PDF文件内容 [" + fileName + "]");
                        System.out.println("=".repeat(80));
                        System.out.println("文件路径: " + pdfFilePath);
                        System.out.println("内容长度: " + content.length() + " 字符");
                        System.out.println("-".repeat(80));
                        System.out.println("内容预览（前500字符）:");
                        if (content.length() > 500) {
                            System.out.println(content.substring(0, 500) + "...");
                        } else {
                            System.out.println(content);
                        }
                        System.out.println("-".repeat(80));
                        System.out.println("=".repeat(80));
                        System.out.println("PDF文件内容结束 [" + fileName + "]");
                        System.out.println("=".repeat(80) + "\n");
                    }
                    
                    // 同时记录到日志
                    logger.info("========== PDF文件内容 [{}] ==========", fileName);
                    logger.info("文件路径: {}", pdfFilePath);
                    logger.info("内容长度: {} 字符", content.length());
                    
                    // 进行向量化并保存到PostgreSQL
                    try {
                        vectorizeAndSave(pdfFilePath, fileName, content, projectId);
                        vectorizeSuccessCount.incrementAndGet();
                        logger.info("PDF文件向量化并保存成功: {}", fileName);
                    } catch (Exception e) {
                        vectorizeFailCount.incrementAndGet();
                        String errorType = "未知错误";
                        String errorDetail = e.getMessage();
                        
                        // 识别错误类型
                        if (e.getMessage() != null) {
                            if (e.getMessage().contains("无法连接到向量化服务") || e.getMessage().contains("Connection refused")) {
                                errorType = "向量化服务连接失败";
                            } else if (e.getMessage().contains("超时") || e.getMessage().contains("timeout")) {
                                errorType = "向量化接口超时";
                            } else if (e.getMessage().contains("状态码")) {
                                errorType = "向量化接口返回错误";
                            } else if (e.getMessage().contains("返回格式错误")) {
                                errorType = "向量化接口返回格式错误";
                            } else if (e.getMessage().contains("网络IO异常")) {
                                errorType = "网络IO异常";
                            }
                        }
                        
                        logger.error("PDF文件向量化失败 [{}]: 文件={}, 错误类型={}, 错误详情={}", 
                                errorType, fileName, errorType, errorDetail, e);
                        
                        // 更新文件状态为失败
                        updateFileVectorizationStatusFailed(pdfFilePath, errorType, errorDetail);
                    }
                    
                    successCount.incrementAndGet();
                    
                } catch (Exception e) {
                    logger.error("读取PDF文件失败: {}, 错误: {}", pdfFilePath, e.getMessage(), e);
                    failCount.incrementAndGet();
                }
            }, executorService);
            
            futures.add(future);
        }
        
        // 等待所有任务完成
        try {
            java.util.concurrent.CompletableFuture.allOf(futures.toArray(new java.util.concurrent.CompletableFuture[0])).join();
        } catch (Exception e) {
            logger.error("并行处理PDF文件时发生异常", e);
        }
        
        long duration = System.currentTimeMillis() - startTime;
        logger.info("[文本解析] ========== 完成! 耗时: {}ms，成功: {}个，失败: {}个，向量化成功: {}个，向量化失败: {}个 ==========", 
                duration, successCount.get(), failCount.get(), vectorizeSuccessCount.get(), vectorizeFailCount.get());
    }

    /**
     * 向量化PDF内容并保存到PostgreSQL
     *
     * @param filePath 文件路径
     * @param fileName 文件名
     * @param content PDF文本内容
     * @param projectId 项目ID
     */
    // 移除 @Transactional，因为我们手动管理 EntityManager 事务，且在多线程环境下私有方法的事务注解无效
    private void vectorizeAndSave(String filePath, String fileName, String content, String projectId) {
        try {
            // 检查是否已经向量化过
            String storedPath = normalizeStoredPath(filePath);
            PdfVector existingVector = pdfVectorRepository.findByFilePath(storedPath);
            if (existingVector != null) {
                logger.debug("PDF文件已存在向量化记录，跳过: {}", storedPath);
                return;
            }

            PdfVector legacyVector = pdfVectorRepository.findByFilePath(filePath);
            if (legacyVector != null) {
                legacyVector.setFilePath(storedPath);
                pdfVectorRepository.save(legacyVector);
                logger.debug("更新历史向量记录路径: {}", storedPath);
                return;
            }

            // 调用向量化服务
            logger.info("开始向量化PDF文件: {}", fileName);
            float[] vector = vectorizationService.vectorizeText(content);
            Integer dimension = vectorizationService.getDimension();
            
            // 构建向量字符串（pgvector格式：[0.1,0.2,0.3,...]）
            StringBuilder vectorStr = new StringBuilder("[");
            for (int i = 0; i < vector.length; i++) {
                if (i > 0) {
                    vectorStr.append(",");
                }
                vectorStr.append(vector[i]);
            }
            vectorStr.append("]");

            // 保存部分内容作为摘要（如果内容太长，只保存前10000字符）
            String contentSummary = content.length() > 10000 ? content.substring(0, 10000) + "..." : content;

            // 首先检查表是否存在，如果不存在则创建（包含vector类型的列）
            EntityManager em = getEntityManager();
            try {
                em.getTransaction().begin();
                
                // 检查表是否存在
                String checkTableSql = "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 't_pdf_vector')";
                Boolean tableExists = (Boolean) em.createNativeQuery(checkTableSql).getSingleResult();
                
                if (!tableExists) {
                    // 创建表，vector列使用vector类型，包含file_id字段
                    String createTableSql = "CREATE TABLE t_pdf_vector (" +
                            "id BIGSERIAL PRIMARY KEY, " +
                            "file_path VARCHAR(1000) NOT NULL, " +
                            "file_name VARCHAR(500) NOT NULL, " +
                            "content TEXT, " +
                            "vector vector, " +
                            "dimension INTEGER NOT NULL, " +
                            "file_id VARCHAR(64), " +
                            "project_id VARCHAR(100), " +
                            "supplier_id VARCHAR(64), " +
                            "file_type_id BIGINT, " +
                            "create_time TIMESTAMP(6) NOT NULL DEFAULT NOW(), " +
                            "update_time TIMESTAMP(6) DEFAULT NOW()" +
                            ")";
                    em.createNativeQuery(createTableSql).executeUpdate();
                    logger.info("创建t_pdf_vector表成功（包含vector类型列和file_id字段）");
                }
                
                // 查找文件ID（关联文件表）
                String fileId = null;
                String supplierId = null;
                Long fileTypeId = null;

                try {
                    java.util.Optional<com.zhpb.gdyd_zhpb_zgf.entity.mysql.File> fileOpt = fileRepository.findByFilePath(storedPath);
                    if (fileOpt.isPresent()) {
                        com.zhpb.gdyd_zhpb_zgf.entity.mysql.File file = fileOpt.get();
                        fileId = file.getFileId();
                        supplierId = file.getSupplierId();
                        fileTypeId = file.getFileTypeId();

                        logger.debug("从数据库获取文件信息: fileId={}, supplierId={}, fileTypeId={}",
                                   fileId, supplierId, fileTypeId);
                    }
                } catch (Exception e) {
                    logger.warn("查找文件ID失败: {}", e.getMessage());
                }

                // 使用原生SQL插入向量数据（包含file_id、supplier_id、file_type_id）
                String insertSql = "INSERT INTO t_pdf_vector (file_path, file_name, content, vector, dimension, file_id, project_id, supplier_id, file_type_id, create_time, update_time) " +
                        "VALUES (:filePath, :fileName, :content, CAST(:vectorStr AS vector), :dimension, :fileId, :projectId, :supplierId, :fileTypeId, NOW(), NOW())";
                
                em.createNativeQuery(insertSql)
                        .setParameter("filePath", storedPath)
                        .setParameter("fileName", fileName)
                        .setParameter("content", contentSummary)
                        .setParameter("vectorStr", vectorStr.toString())
                        .setParameter("dimension", dimension)
                        .setParameter("fileId", fileId)
                        .setParameter("projectId", projectId)
                        .setParameter("supplierId", supplierId)
                        .setParameter("fileTypeId", fileTypeId)
                        .executeUpdate();
                        
                em.getTransaction().commit();
                
                // 更新文件的向量化状态和数量
                if (fileId != null) {
                    updateFileVectorizationStatus(fileId);
                }
            } catch (Exception e) {
                em.getTransaction().rollback();
                throw e;
            } finally {
                em.close();
            }
            
            logger.info("PDF向量化并保存成功，文件: {}，维度: {}", fileName, dimension);
            
        } catch (Exception e) {
            logger.error("向量化并保存PDF失败: {}, 错误: {}", fileName, e.getMessage(), e);
            throw new RuntimeException("向量化并保存PDF失败: " + e.getMessage(), e);
        }
    }

    /**
     * 读取PDF文件内容
     *
     * @param pdfPath PDF文件路径
     * @return PDF文本内容
     * @throws IOException 读取异常
     */
    private String readPdfContent(Path pdfPath) throws IOException {
        java.io.File file = pdfPath.toFile();

        // 检查文件是否存在
        if (!file.exists()) {
            throw new IOException("PDF文件不存在: " + pdfPath);
        }

        // 检查文件大小
        if (file.length() == 0) {
            throw new IOException("PDF文件为空文件: " + pdfPath);
        }

        // 检查文件是否过小（PDF文件最小应该有几个KB）
        if (file.length() < 1024) {
            throw new IOException("PDF文件过小，可能已损坏，文件大小: " + file.length() + " bytes, 路径: " + pdfPath);
        }

        // 检查PDF文件头是否正确
        try (java.io.RandomAccessFile raf = new java.io.RandomAccessFile(file, "r")) {
            byte[] header = new byte[8];
            int bytesRead = raf.read(header);
            if (bytesRead < 8) {
                throw new IOException("PDF文件头部不完整，读取到 " + bytesRead + " 字节，路径: " + pdfPath);
            }

            String headerStr = new String(header, "ISO-8859-1");
            if (!headerStr.startsWith("%PDF-")) {
                throw new IOException("无效的PDF文件头部: " + headerStr + ", 路径: " + pdfPath);
            }

            logger.debug("PDF文件头部验证通过: {}", headerStr);
        }

        // 设置PDFBox配置以更好地处理损坏的文件
        java.io.FileInputStream fis = null;
        PDDocument document = null;

        try {
            fis = new java.io.FileInputStream(file);

            // 使用更宽松的加载方式
            org.apache.pdfbox.io.RandomAccessReadBuffer buffer = new org.apache.pdfbox.io.RandomAccessReadBuffer(fis);
            document = Loader.loadPDF(buffer);

            // 检查文档是否为空
            if (document.getNumberOfPages() == 0) {
                throw new IOException("PDF文档页数为0，可能已损坏: " + pdfPath);
            }

            PDFTextStripper stripper = new PDFTextStripper();

            // 设置文本提取参数
            stripper.setStartPage(1);
            stripper.setEndPage(Math.min(document.getNumberOfPages(), 100)); // 限制最大页数，避免处理过大的文档
            stripper.setSortByPosition(true); // 按位置排序文本

            String text = stripper.getText(document);

            // 检查提取的文本是否为空或过短
            if (text == null || text.trim().isEmpty()) {
                logger.warn("PDF文件未提取到有效文本内容: {}", pdfPath);
                text = "[PDF文件内容为空或无法提取文本]";
            } else if (text.trim().length() < 10) {
                logger.warn("PDF文件提取的文本过短，可能有问题: {}，内容: {}", pdfPath, text.trim());
            }

            logger.info("成功读取PDF文件: {}，共 {} 页，内容长度: {} 字符",
                    pdfPath.getFileName(), document.getNumberOfPages(), text.length());

            return text;

        } catch (RuntimeException e) {
            // PDFBox特定的运行时异常，通常表示文件损坏
            // 检查是否为PDFBox相关的异常
            if (e.getClass().getName().contains("pdfbox") ||
                e.getMessage() != null && e.getMessage().contains("COS")) {
                logger.error("PDF文件结构损坏，无法读取: {}，错误: {}", pdfPath, e.getMessage());
                throw new IOException("PDF文件结构损坏: " + e.getMessage() + ", 路径: " + pdfPath, e);
            } else {
                throw e; // 重新抛出非PDFBox异常
            }
        } catch (Exception e) {
            // 其他异常
            logger.error("读取PDF文件失败: {}，错误: {}", pdfPath, e.getMessage());
            if (e instanceof IOException) {
                throw e;
            } else {
                throw new IOException("读取PDF文件失败: " + e.getMessage() + ", 路径: " + pdfPath, e);
            }
        } finally {
            // 确保资源被正确释放
            if (document != null) {
                try {
                    document.close();
                } catch (Exception e) {
                    logger.warn("关闭PDF文档失败: {}", e.getMessage());
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (Exception e) {
                    logger.warn("关闭文件输入流失败: {}", e.getMessage());
                }
            }
        }
    }


    /**
     * 批量更新文件向量化状态为处理中
     */
    private void updateFilesVectorizationStatus(List<String> filePaths, String status) {
        try {
            for (String filePath : filePaths) {
                String storedPath = normalizeStoredPath(filePath);
                java.util.Optional<com.zhpb.gdyd_zhpb_zgf.entity.mysql.File> fileOpt = fileRepository.findByFilePath(storedPath);
                if (fileOpt.isPresent()) {
                    com.zhpb.gdyd_zhpb_zgf.entity.mysql.File file = fileOpt.get();
                    file.setVectorizationStatus(status);
                    fileRepository.save(file);
                }
            }
        } catch (Exception e) {
            logger.warn("批量更新文件向量化状态失败: {}", e.getMessage());
        }
    }

    private void updateFileVectorizationStatusFailed(String filePath, String errorType, String errorDetail) {
        try {
            String storedPath = normalizeStoredPath(filePath);
            java.util.Optional<com.zhpb.gdyd_zhpb_zgf.entity.mysql.File> fileOpt = fileRepository.findByFilePath(storedPath);
            if (fileOpt.isPresent()) {
                com.zhpb.gdyd_zhpb_zgf.entity.mysql.File file = fileOpt.get();
                file.setVectorizationStatus("failed");
                file.setStatus("vectorization_failed");
                
                // 如果提供了错误信息，更新描述字段
                if (errorType != null && errorDetail != null) {
                    String description = String.format("向量化失败: %s - %s", errorType, 
                            errorDetail.length() > 200 ? errorDetail.substring(0, 200) + "..." : errorDetail);
                    file.setDescription(description);
                }
                
                fileRepository.save(file);
                logger.debug("文件向量化状态已更新为失败: {}, 错误类型: {}", storedPath, errorType);
            }
        } catch (Exception e) {
            logger.warn("更新文件向量化失败状态失败: {}", e.getMessage());
        }
    }

    /**
     * 更新文件的向量化状态和数量
     */
    private void updateFileVectorizationStatus(String fileId) {
        try {
            java.util.Optional<com.zhpb.gdyd_zhpb_zgf.entity.mysql.File> fileOpt = fileRepository.findByFileId(fileId);
            if (fileOpt.isPresent()) {
                com.zhpb.gdyd_zhpb_zgf.entity.mysql.File file = fileOpt.get();
                
                // 统计该文件对应的向量化记录数量
                long vectorCount = pdfVectorRepository.countByFileId(fileId);
                
                // 更新向量化状态和数量
                file.setVectorizationCount((int) vectorCount);
                if (vectorCount > 0) {
                    file.setIsVectorized(true);
                    file.setVectorizationStatus("completed");
                    file.setVectorizedTime(java.time.LocalDateTime.now());
                } else {
                    file.setIsVectorized(false);
                    file.setVectorizationStatus("not_started");
                }
                
                file.setStatus("vectorized");
                fileRepository.save(file);
                
                logger.debug("更新文件向量化状态: fileId={}, vectorizationCount={}, status={}", 
                        fileId, vectorCount, file.getVectorizationStatus());
            }
        } catch (Exception e) {
            logger.warn("更新文件向量化状态失败: {}", e.getMessage());
        }
    }

    private String normalizeStoredPath(String filePath) {
        if (filePath == null || filePath.isBlank()) {
            return filePath;
        }
        try {
            Path path = Paths.get(filePath).toAbsolutePath().normalize();
            return filePathResolver.normalizeForStorage(path);
        } catch (Exception e) {
            return filePath;
        }
    }
}

