package top.r3944realms.docchecktoolrefactored.io.scanner;

import lombok.extern.slf4j.Slf4j;
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
public class RobustParallelScanner implements FileScanner {
    private final ForkJoinPool forkJoinPool;
    private volatile boolean cancelled = false;

    // 用于记录当前正在扫描的路径
    private final AtomicReference<Path> currentScanningPath = new AtomicReference<>();
    private final AtomicReference<String> lastProcessedFile = new AtomicReference<>();
    private final AtomicInteger totalFilesScanned = new AtomicInteger(0);
    private final AtomicInteger totalDirectoriesScanned = new AtomicInteger(0);
    private volatile LocalDateTime scanStartTime;

    private final int maxDepth;
    public RobustParallelScanner(int maxDepth) {
        this(Runtime.getRuntime().availableProcessors(), maxDepth);
    }
    public RobustParallelScanner(int parallelism, int maxDepth) {
        this.forkJoinPool = new ForkJoinPool(parallelism);
        this.maxDepth = maxDepth; // 防止无限递归
    }
    @Override
    public void scan(Path rootPath, FileScanListener listener, long timeout) {
        scanInternal(rootPath, listener, null, timeout);
    }
    @Override
    public void scan(Path rootPath, FileScanListener listener) {
        scan(rootPath, listener, 30);
    }
    @Override
    public void scanWithProgress(Path rootPath, ProgressAwareListener listener, long timeout) {
        // 预扫描阶段：计算总文件数
        AtomicLong totalFiles = new AtomicLong(0);
        countFiles(rootPath, totalFiles);
        scanInternal(rootPath, listener, totalFiles, timeout);
    }
    @Override
    public void scanWithProgress(Path rootPath, ProgressAwareListener listener) {
        scanWithProgress(rootPath, listener, 30);
    }
    private void countFiles(Path dir, AtomicLong counter) {
        if (cancelled) return;

        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
            for (Path path : stream) {
                if (cancelled) return;

                if (Files.isDirectory(path)) {
                    countFiles(path, counter);
                } else if (Files.isRegularFile(path)) {
                    counter.incrementAndGet();
                }
            }
        } catch (IOException e) {
            log.warn(LoggerMarker.TRACE_MARKER, "Failed to pre-scan: {}", dir, e);
        }
    }
    private void scanInternal(Path rootPath, FileScanListener listener, AtomicLong totalFiles) {
        scanInternal(rootPath, listener, totalFiles, 30);
    }
    private void scanInternal(Path rootPath, FileScanListener listener, AtomicLong totalFiles, long timeout) {
        resetScanState();
        scanStartTime = LocalDateTime.now();
        currentScanningPath.set(rootPath);

        try {
            validateDirectory(rootPath);

            ForkJoinTask<?> submit = forkJoinPool.submit(() -> {
                try {
                    AtomicInteger processedFiles = new AtomicInteger(0);
                    scanDirectory(rootPath, listener, processedFiles, totalFiles, 0);

                    if (!cancelled) {
                        listener.onScanComplete();
                        logScanStatistics();
                    }
                } catch (Exception e) {
                    listener.onError(rootPath, e);
                }
            });
            handleTimeout(submit, rootPath, listener, timeout);
        } catch (Exception e) {
            listener.onError(rootPath, e);
        }
    }

    private void handleTimeout(Future<?> scanFuture, Path rootPath,
                               FileScanListener listener, long timeout) throws TimeoutException {
        try {
            scanFuture.get(timeout, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            // 获取详细的超时信息
            String timeoutInfo = buildTimeoutInfo(rootPath);
            log.error(LoggerMarker.RELEASE_MARKER, "扫描超时 - {}", timeoutInfo, e);

            // 取消扫描任务
            cancelled = true;
            scanFuture.cancel(true);
            forkJoinPool.shutdownNow();
            log.info("扫描超时 - {}", timeoutInfo);
            // 创建详细的超时异常
            throw new TimeoutException(
                    String.format("扫描超时: 已执行 %d 秒\n%s", timeout, timeoutInfo)
            );
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            listener.onError(rootPath, new InterruptedException("扫描被中断"));
        } catch (ExecutionException e) {
            listener.onError(rootPath, e);
        }
    }

    private void resetScanState() {
        cancelled = false;
        currentScanningPath.set(null);
        lastProcessedFile.set(null);
        totalFilesScanned.set(0);
        totalDirectoriesScanned.set(0);
    }

    private String buildTimeoutInfo(Path rootPath) {
        return "扫描开始时间: " + formatDateTime(scanStartTime) + "\n" +
                "扫描根目录: " + rootPath.toAbsolutePath() + "\n" +
                "当前扫描路径: " + currentScanningPath.get() + "\n" +
                "最后处理的文件: " + lastProcessedFile.get() + "\n" +
                "已扫描文件数: " + totalFilesScanned.get() + "\n" +
                "已扫描目录数: " + totalDirectoriesScanned.get() + "\n"
               ;
    }
    private void logScanStatistics() {
        LocalDateTime endTime = LocalDateTime.now();
        log.info(LoggerMarker.DEBUG_MARKER, """
                扫描统计信息:
                开始时间: {}
                结束时间: {}
                总扫描时间: {} 秒
                扫描文件数: {}
                扫描目录数: {}
                最后扫描路径: {}
                """,
                formatDateTime(scanStartTime),
                formatDateTime(endTime),
                java.time.Duration.between(scanStartTime, endTime).getSeconds(),
                totalFilesScanned.get(),
                totalDirectoriesScanned.get(),
                currentScanningPath.get()
        );
    }

    private void scanDirectory(Path dir, FileScanListener listener,
                               AtomicInteger processedFiles, AtomicLong totalFiles, int currentDepth) {
        if (cancelled || currentDepth > maxDepth) return;

        currentScanningPath.set(dir);
        totalDirectoriesScanned.incrementAndGet();

        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
            for (Path path : stream) {
                if (cancelled) break;

                if (Files.isDirectory(path)) {
                    // 记录目录扫描开始
                    log.trace(LoggerMarker.TRACE_MARKER, "扫描目录: {}, 深度: {}", path, currentDepth + 1);
                    scanDirectory(path, listener, processedFiles, totalFiles, currentDepth + 1);
                } else if (Files.isRegularFile(path)) {
                    lastProcessedFile.set(path.toAbsolutePath().toString());
                    processFile(path, listener, processedFiles, totalFiles);
                }
            }
        } catch (IOException e) {
            log.warn(LoggerMarker.TRACE_MARKER, "无法访问目录: {}", dir, e);
            listener.onError(dir, e);
        }
    }
    private void processFile(Path file, FileScanListener listener,
                             AtomicInteger processedFiles, AtomicLong totalFiles) {
        if (cancelled) return;

        try {
            listener.onFileFound(file);
            totalFilesScanned.incrementAndGet();

            // 进度更新处理
            if (totalFilesScanned.get() % 1000 == 0) {
                log.debug(LoggerMarker.TRACE_MARKER, "已扫描 {} 个文件，当前文件: {}",
                        totalFilesScanned.get(), file);
            }
            if (listener instanceof ProgressAwareListener progressListener) {
                int processed = processedFiles.incrementAndGet();
                long total = totalFiles.get();
                progressListener.onProgressUpdate(processed, (int)total);
            }
        } catch (Exception e) {
            log.warn(LoggerMarker.TRACE_MARKER, "处理文件失败: {}", file, e);
            listener.onError(file, e);
        }
    }

    private void validateDirectory(Path path) throws IOException {
        if (!Files.exists(path)) {
            throw new FileNotFoundException(path.toString());
        }
        if (!Files.isReadable(path)) {
            throw new AccessDeniedException(path.toString());
        }
        // 检查是否是挂载点
        if (Files.getFileStore(path).type().equals("NTFS") &&
                path.toString().contains("$")) {
            throw new IOException("系统目录禁止访问: " + path);
        }
        long freeSpace = Files.getFileStore(path).getUsableSpace();
        if (freeSpace < 1024 * 1024 * 10) { // 小于10MB
            log.warn(LoggerMarker.TRACE_MARKER, "磁盘空间不足: {}，可用空间: {} MB",
                    path, freeSpace / (1024 * 1024));
        }
    }

    private String formatDateTime(LocalDateTime dateTime) {
        if (dateTime == null) return "N/A";
        return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    @Override
    public void cancel() {
        cancelled = true;
        forkJoinPool.shutdownNow();
    }

    @Override
    public void close() {
        cancel();
    }
}