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.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

@Slf4j
public class RobustParallelScanner implements FileScanner {
    private final ForkJoinPool forkJoinPool;
    private volatile boolean cancelled = false;
    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) {
        try {
            validateDirectory(rootPath);

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

                    if (!cancelled) {
                        listener.onScanComplete();
                    }
                } catch (Exception e) {
                    listener.onError(rootPath, e);
                }
            }).get(timeout, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            log.error(LoggerMarker.TRACE_MARKER, "Scan timeout: {}", rootPath, e);
            forkJoinPool.shutdownNow();
            listener.onError(rootPath, new TimeoutException("扫描超时30秒"));
        } catch (Exception e) {
            listener.onError(rootPath, e);
        }
    }

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

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

                if (Files.isDirectory(path)) {
                    scanDirectory(path, listener, processedFiles, totalFiles, currentDepth + 1);
                } else if (Files.isRegularFile(path)) {
                    processFile(path, listener, processedFiles, totalFiles);
                }
            }
        } catch (IOException e) {
            listener.onError(dir, e);
        }
    }
    private void processFile(Path file, FileScanListener listener,
                             AtomicInteger processedFiles, AtomicLong totalFiles) {
        if (cancelled) return;

        try {
            listener.onFileFound(file);

            // 进度更新处理
            if (listener instanceof ProgressAwareListener progressListener && totalFiles != null) {
                int processed = processedFiles.incrementAndGet();
                long total = totalFiles.get();
                progressListener.onProgressUpdate(processed, (int)total);
            }
        } catch (Exception 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);
        }
    }

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

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