package top.r3944realms.docchecktoolrefactored.core;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import top.r3944realms.docchecktoolrefactored.System;
import top.r3944realms.docchecktoolrefactored.ThreadPoolManager;
import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
import top.r3944realms.docchecktoolrefactored.model.DuplicateGroup;
import top.r3944realms.docchecktoolrefactored.model.FileMetadata;
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;


/**
 * 重复文件查找核心类
 */
@Slf4j
public class DuplicateFinder {
    private final FileScanner fileScanner;
    private final FileHashCalculator hashCalculator;
    private final boolean enableProgress;
    private final ExecutorService executorService;
    private final AtomicInteger targetFiles = new AtomicInteger(0);
    private final AtomicInteger otherFiles = new AtomicInteger(0);

// Add getter methods
public int getTargetFilesCount() {
    return targetFiles.get();
}

public int getOtherFilesCount() {
    return otherFiles.get();
}
    // 进度回调接口
    public interface ProgressCallback {
        default void onPhaseStarted(Phase phase) {}
        default void onPhaseProgress(Phase phase, int current, int total) {}
        default void onPhaseCompleted(Phase phase) {}
    }
    public enum Phase {
        GROUP_BY_SIZE,      // 按大小分组阶段
        CALCULATE_HASH      // 计算哈希阶段
    }
    @Setter
    private ProgressCallback progressCallback;
    private static final int PROGRESS_REPORT_INTERVAL = 100;
    @Getter
    private final List<Exception> errors = new CopyOnWriteArrayList<>();
    @Getter
    private long timeout = -1;
    public DuplicateFinder(FileScanner fileScanner, FileHashCalculator hashCalculator, boolean enableProgress) {
        this.fileScanner = Objects.requireNonNull(fileScanner);
        this.hashCalculator = Objects.requireNonNull(hashCalculator);
        this.enableProgress = enableProgress;
        this.executorService = ThreadPoolManager.createPool("duplicate-finder-pool", System.getAvailableProcessors());
    }
    public DuplicateFinder(FileScanner fileScanner, FileHashCalculator hashCalculator) {
        this(fileScanner, hashCalculator, false);
    }

    public DuplicateFinder applySetting(Setting setting) {
        this.timeout = setting.getScanTimeout();
        return this;
    }

    /**
     * 查找重复文件
     * @param rootDir 要扫描的根目录
     * @return 按哈希值分组的重复文件列表
     */
    public List<DuplicateGroup> findDuplicates(Path rootDir) throws IOException {
        // 清理错误列表
        errors.clear();
        targetFiles.set(0);
        otherFiles.set(0);
        // -----------------------------
        // 第一阶段：按文件大小分组
        // -----------------------------
        if (progressCallback != null) {
            progressCallback.onPhaseStarted(Phase.GROUP_BY_SIZE);
        }
        Map<Long, List<FileMetadata>> sizeGroups = groupFilesBySize(rootDir);

        if (progressCallback != null) {
            progressCallback.onPhaseCompleted(Phase.GROUP_BY_SIZE);
        }

        // -----------------------------
        // 第二阶段：按文件组计算哈希
        // -----------------------------
        if (progressCallback != null) progressCallback.onPhaseStarted(Phase.CALCULATE_HASH);

        Map<String, List<FileMetadata>> hashGroups = new ConcurrentHashMap<>();
        AtomicInteger processedFiles = new AtomicInteger(0);

        // 获取候选文件组（每组内至少2个文件）
        List<List<FileMetadata>> candidateGroups = sizeGroups.values().stream()
                .filter(group -> group.size() > 1)
                .toList();

        int totalFilesToProcess = candidateGroups.stream().mapToInt(List::size).sum();
        if (totalFilesToProcess == 0) return Collections.emptyList();

        List<CompletableFuture<Void>> futures = new ArrayList<>();

        // 分批提交线程池，每组作为一个批次
        for (List<FileMetadata> group : candidateGroups) {
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                for (FileMetadata file : group) {
                    processFile(file, hashGroups, processedFiles, totalFilesToProcess);
                }
            }, executorService);
            futures.add(future);
        }

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        if (progressCallback != null) progressCallback.onPhaseCompleted(Phase.CALCULATE_HASH);
        if (enableProgress) java.lang.System.out.println();

        // -----------------------------
        // 第三阶段：构建结果
        // -----------------------------
        return hashGroups.values().stream()
                .filter(group -> group.size() > 1)
                .map(group -> new DuplicateGroup(
                        group.get(0).getHash(),
                        group.get(0).getSize(),
                        group
                ))
                .sorted(Comparator.comparingLong(DuplicateGroup::size).reversed())
                .collect(Collectors.toList());
    }

    /**
     * 按文件大小分组
     */
    private Map<Long, List<FileMetadata>> groupFilesBySize(Path rootDir) throws IOException {
        Map<Long, List<FileMetadata>> sizeGroups = new ConcurrentHashMap<>();
        boolean flag = timeout != -1 && timeout > 0;
        FileScanner.ProgressAwareListener listener = new FileScanner.ProgressAwareListener() {
            @Override
            public void onProgressUpdate(int current, int total) {
                if (enableProgress) {
                    printProgress("Scanning files", current, total);
                }
                if (progressCallback != null) {
                    progressCallback.onPhaseProgress(Phase.GROUP_BY_SIZE, current, total);
                }
            }

            @Override
            public void onFileFound(Path file) {
                try {
                    FileMetadata meta = new FileMetadata();
                    meta.setPath(file);
                    meta.setSize(Files.size(file));
                    sizeGroups.computeIfAbsent(meta.getSize(), k -> new ArrayList<>()).add(meta);

                    // Classify and count files
                    String fileName = file.getFileName().toString().toLowerCase();
                    String extension = "";
                    int lastDotIndex = fileName.lastIndexOf('.');
                    if (lastDotIndex > 0) {
                        extension = fileName.substring(lastDotIndex + 1);
                    }

                    // Check if it's a target file
                    if ("pdf".equals(extension) || "ofd".equals(extension) ||
                        "jpg".equals(extension) || "jpeg".equals(extension) ||
                        "png".equals(extension) || "bmp".equals(extension) ||
                        "gif".equals(extension) || "tiff".equals(extension) ||
                        "jp2".equals(extension)) {
                        targetFiles.incrementAndGet();
                    } else {
                        otherFiles.incrementAndGet();
                    }
                } catch (IOException e) {
                    log.error(LoggerMarker.TRACE_MARKER, "Failed to get file's size: {}", file);
                }
            }

            @Override public void onScanComplete() {}
            @Override public void onError(Path file, Exception e) {
                log.error(LoggerMarker.TRACE_MARKER, "Error on scanning file: {}, {}", file, e.getMessage());
                errors.add(e);
            }
        };
        if(enableProgress)
            if (flag) fileScanner.scanWithProgress(rootDir, listener, timeout); else fileScanner.scanWithProgress(rootDir, listener);
        else
            if (flag) fileScanner.scan(rootDir, listener, timeout); else fileScanner.scan(rootDir, listener);
        return sizeGroups;
    }
    /**
     * 打印进度条
     * @param phase 当前阶段描述
     * @param current 当前进度
     * @param total 总进度
     */
    private void printProgress(String phase, int current, int total) {
        int width = 50; // 进度条宽度
        float percent = (float) current / total;
        int progress = (int) (width * percent);

        String progressBar = String.format("\r%s: [%-" + width + "s] %3d%% %d/%d",
                phase,
                "=".repeat(progress),
                (int) (percent * 100),
                current,
                total);

        java.lang.System.out.print(progressBar);
    }
    private void processFile(FileMetadata file,
                             Map<String, List<FileMetadata>> hashGroups,
                             AtomicInteger processedFiles,
                             int totalFilesToProcess) {
        try {
            String hash = file.getSize() > 10_000_000 ?
                    hashCalculator.calculateHashStreaming(file.getPath()) :
                    hashCalculator.calculateHash(file.getPath());

            file.setHash(hash);
            //hashGroups已经定义为ConcurrentHashMap，可以利用它本身的线程安全方法来减少同步块
            //synchronized (hashGroups) {
                hashGroups.computeIfAbsent(hash, k -> new ArrayList<>()).add(file);
            //}

            int current = processedFiles.incrementAndGet();
            if (enableProgress && (current % PROGRESS_REPORT_INTERVAL == 0 || current == totalFilesToProcess)) {
                printProgress("Calculating hashes", current, totalFilesToProcess);
            }

            if (progressCallback != null) {
                progressCallback.onPhaseProgress(Phase.CALCULATE_HASH, current, totalFilesToProcess);
            }

        } catch (IOException e) {
            log.error(LoggerMarker.TRACE_MARKER, "Failed to calculate file's hash: {}, {}", file.getPath(), e.getMessage());
            errors.add(e);
        }
    }
    public void shutdown() {
        fileScanner.cancel();
        executorService.shutdown();
    }
}