package com.weather.modules.weather.filescan;

import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.weather.common.constant.Constant;
import com.weather.modules.sys.dao.SysDeptDao;
import com.weather.modules.sys.entity.SysDeptEntity;
import com.weather.modules.sys.service.SysParamsService;
import com.weather.modules.weather.filescan.dao.WeatherFileScanRecordDao;
import com.weather.modules.weather.filescan.entity.WeatherFileScanRecordEntity;
import com.weather.modules.weather.filescan.utils.FileNameParser;
import com.weather.modules.weather.filescan.utils.ParseResult;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static java.nio.file.StandardWatchEventKinds.*;

@Slf4j
@Component
public class FileWatchServiceManager {

    private final SysParamsService sysParamsService;
    private final SysDeptDao sysDeptDao;
    private final WeatherFileScanRecordDao weatherFileScanRecordDao;

    public FileWatchServiceManager(SysParamsService sysParamsService, SysDeptDao sysDeptDao, WeatherFileScanRecordDao weatherFileScanRecordDao) {
        this.sysParamsService = sysParamsService;
        this.sysDeptDao = sysDeptDao;
        this.weatherFileScanRecordDao = weatherFileScanRecordDao;
    }

    /** 需要跳过的文件扩展名 */
    private static final Set<String> SKIP_EXTENSIONS = Set.of("db");

    /** 需要跳过的目录名 */
    private static final Set<String> SKIP_DIR_NAMES = Set.of("新建文件夹");

    private WatchService watchService;
    final Map<String, Long> deptDirMap = new LinkedHashMap<>();
    private String rootReceiveDir;
    private volatile boolean running = false;

    /** 文件处理线程池，用于并发 MD5 计算 + 入库 */
    private final ExecutorService fileProcessExecutor = Executors.newFixedThreadPool(4);

    @PostConstruct
    public void init() {
        try {
            watchService = FileSystems.getDefault().newWatchService();
            running = true;
            new Thread(this::processWatchEvents, "file-watch-service").start();
            log.info("WatchService 启动成功");
        } catch (IOException e) {
            log.error("WatchService 启动失败", e);
        }
    }

    @PreDestroy
    public void destroy() {
        running = false;
        if (watchService != null) {
            try {
                watchService.close();
            } catch (IOException e) {
                log.warn("WatchService 关闭异常", e);
            }
        }
        fileProcessExecutor.shutdown();
    }

    /** 确保文件扫描目录结构存在（receive/display/archive 及各部门子目录） */
    public void ensureDirectoriesExist() {
        String rootPath = sysParamsService.getValue(Constant.FILE_SCAN_ROOT_PATH);
        if (StrUtil.isBlank(rootPath) || "0".equals(rootPath)) {
            return;
        }

        List<String> rootDirs = List.of("receive", "display", "archive");
        for (String sub : rootDirs) {
            Path dir = Paths.get(rootPath, sub);
            try {
                Files.createDirectories(dir);
                log.debug("确保根目录存在: {}", dir);
            } catch (IOException e) {
                log.error("创建根目录失败: {}", dir, e);
            }
        }

        List<SysDeptEntity> deptList = sysDeptDao.selectList(null);
        for (SysDeptEntity dept : deptList) {
            if (StrUtil.isBlank(dept.getName())) continue;
            for (String sub : rootDirs) {
                Path dir = Paths.get(rootPath, sub, dept.getName());
                try {
                    Files.createDirectories(dir);
                    log.debug("确保部门目录存在: {}", dir);
                } catch (IOException e) {
                    log.error("创建部门目录失败: {}", dir, e);
                }
            }
        }
    }

    /** 注册部门目录 + 根目录的 WatchService 监控 */
    public void registerDeptDirectories() {
        String rootPath = sysParamsService.getValue(Constant.FILE_SCAN_ROOT_PATH);
        if (StrUtil.isBlank(rootPath) || "0".equals(rootPath)) {
            log.warn("文件扫描根路径未配置，请检查 sys_params 中的 FILE_SCAN_ROOT_PATH");
            return;
        }

        Path receiveRoot = Paths.get(rootPath, "receive");
        List<SysDeptEntity> deptList = sysDeptDao.selectList(null);
        deptDirMap.clear();

        // 注册各部门子目录
        for (SysDeptEntity dept : deptList) {
            if (StrUtil.isBlank(dept.getName())) continue;
            Path dir = receiveRoot.resolve(dept.getName());
            try {
                Files.createDirectories(dir);
                dir.register(watchService, ENTRY_CREATE, ENTRY_MODIFY);
                deptDirMap.put(dir.toString(), dept.getId());
                log.debug("注册 WatchService 监控目录: {}", dir);
            } catch (IOException e) {
                log.error("创建/注册目录失败: {}", dir, e);
            }
        }

        // 注册根目录（用于模式预报 gif 等非部门文件）
        try {
            Files.createDirectories(receiveRoot);
            receiveRoot.register(watchService, ENTRY_CREATE, ENTRY_MODIFY);
            rootReceiveDir = receiveRoot.toString();
            log.debug("注册 WatchService 监控根目录: {}", receiveRoot);
        } catch (IOException e) {
            log.error("注册根目录监控失败: {}", receiveRoot, e);
        }
    }

    /** 全量扫描：部门子目录 + 根目录直接文件 */
    public void scanAllDirectories() {
        String rootPath = sysParamsService.getValue(Constant.FILE_SCAN_ROOT_PATH);
        if (StrUtil.isBlank(rootPath) || "0".equals(rootPath)) return;

        Path receiveRoot = Paths.get(rootPath, "receive");
        if (!Files.exists(receiveRoot)) return;

        List<SysDeptEntity> deptList = sysDeptDao.selectList(null);
        Map<String, Long> deptNameIdMap = new HashMap<>();
        for (SysDeptEntity dept : deptList) {
            if (StrUtil.isNotBlank(dept.getName())) {
                deptNameIdMap.put(dept.getName(), dept.getId());
            }
        }

        try (var dirStream = Files.list(receiveRoot)) {
            dirStream.forEach(entry -> {
                if (Files.isDirectory(entry)) {
                    // 部门子目录
                    String deptName = entry.getFileName().toString();
                    if (SKIP_DIR_NAMES.contains(deptName)) {
                        log.debug("跳过非部门目录: {}", deptName);
                        return;
                    }
                    Long deptId = deptNameIdMap.get(deptName);
                    if (deptId == null) {
                        log.debug("跳过未匹配部门的目录: {}", deptName);
                        return;
                    }
                    scanDirectory(entry, deptId);
                } else if (Files.isRegularFile(entry)) {
                    // 根目录直接文件（模型预报图等）
                    processFile(entry, null);
                }
            });
        } catch (IOException e) {
            log.error("扫描根目录失败: {}", receiveRoot, e);
        }
    }

    /** 扫描部门目录下的所有文件 */
    private void scanDirectory(Path deptDir, Long deptId) {
        try (var fileStream = Files.list(deptDir)) {
            fileStream.filter(Files::isRegularFile).forEach(file ->
                    fileProcessExecutor.submit(() -> processFile(file, deptId)));
        } catch (IOException e) {
            log.error("扫描目录失败: {}", deptDir, e);
        }
    }

    /** WatchService 事件处理循环 */
    private void processWatchEvents() {
        while (running) {
            try {
                WatchKey key = watchService.take();
                Path watchDir = (Path) key.watchable();

                for (WatchEvent<?> event : key.pollEvents()) {
                    WatchEvent.Kind<?> kind = event.kind();
                    if (kind == OVERFLOW) continue;

                    Path fileName = (Path) event.context();
                    Path fullPath = watchDir.resolve(fileName);

                    if (!Files.isRegularFile(fullPath)) continue;

                    Long deptId = deptDirMap.get(watchDir.toString());
                    // deptDirMap 中没有的是根目录，deptId 为 null
                    fileProcessExecutor.submit(() -> processFile(fullPath, deptId));
                }

                if (!key.reset()) {
                    log.warn("WatchKey 已失效: {}", watchDir);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
                log.error("WatchService 事件处理异常", e);
            }
        }
    }

    /**
     * 等待文件写入完成并可读。
     * 先快速路径尝试获取共享锁（已有文件通常立即可用），
     * 若被占用再进入大小稳定 + 等待循环。
     */
    private boolean waitForFileReady(Path filePath) {
        if (!Files.exists(filePath)) return false;

        // 快速路径：已写完的文件可立即获得共享锁，直接返回
        try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "rw");
             FileChannel ch = raf.getChannel()) {
            java.nio.channels.FileLock lock = ch.lock(0, Long.MAX_VALUE, true);
            lock.release();
            return true;
        } catch (Exception ignored) {
            // 文件正在被写入，进入等待逻辑
        }

        long lastSize = -1;
        int stableCount = 0;
        int maxRetries = 20;
        for (int i = 0; i < maxRetries; i++) {
            if (!Files.exists(filePath)) return false;
            try {
                long currentSize = Files.size(filePath);
                if (currentSize == lastSize && currentSize > 0) {
                    stableCount++;
                    if (stableCount >= 2) {
                        try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "rw");
                             FileChannel ch = raf.getChannel()) {
                            java.nio.channels.FileLock lock = ch.lock(0, Long.MAX_VALUE, true);
                            lock.release();
                            return true;
                        } catch (Exception e) {
                            log.debug("文件仍被占用，等待中: {} ({})", filePath.getFileName(), e.getMessage());
                        }
                    }
                } else {
                    stableCount = 0;
                }
                lastSize = currentSize;
            } catch (IOException e) {
                log.debug("文件暂不可读，等待中: {}", filePath.getFileName());
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        log.warn("文件等待超时，跳过: {}", filePath.getFileName());
        return false;
    }

    /** 处理单个文件入库（deptId 为 null 表示根目录文件，不绑定部门） */
    private void processFile(Path filePath, Long deptId) {
        try {
            String fileName = filePath.getFileName().toString();

            // 跳过不需要的文件类型
            String ext = "";
            int dotIdx = fileName.lastIndexOf('.');
            if (dotIdx > 0) {
                ext = fileName.substring(dotIdx + 1).toLowerCase();
            }
            if (SKIP_EXTENSIONS.contains(ext)) {
                log.debug("跳过忽略的文件类型: {}", fileName);
                return;
            }

            if (!waitForFileReady(filePath)) return;

            String md5 = DigestUtil.md5Hex(filePath.toFile());

            long existingCount = weatherFileScanRecordDao.selectCount(
                    new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<WeatherFileScanRecordEntity>()
                            .eq("md5_hash", md5)
            );
            if (existingCount > 0) {
                log.debug("文件已存在，跳过: {} (MD5: {})", fileName, md5);
                return;
            }

            BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
            Date fileCreateTime = new Date(attrs.creationTime().toMillis());

            String rootPath = sysParamsService.getValue(Constant.FILE_SCAN_ROOT_PATH);
            Path displayRoot = Paths.get(rootPath, "display");

            Path displayTarget;
            Path archiveDir;

            if (deptId == null) {
                // 根目录文件（模型预报图等）：直接放在 display/ 和 archive/ 下
                displayTarget = displayRoot.resolve(fileName);
                archiveDir = Paths.get(rootPath, "archive");
            } else {
                // 部门文件：放在 display/<部门>/ 和 archive/<部门>/ 下
                SysDeptEntity dept = sysDeptDao.selectById(deptId);
                String deptName = dept != null ? dept.getName() : String.valueOf(deptId);
                Path displayDeptDir = displayRoot.resolve(deptName);
                Files.createDirectories(displayDeptDir);
                displayTarget = displayDeptDir.resolve(fileName);
                archiveDir = Paths.get(rootPath, "archive", deptName);
            }

            Files.createDirectories(displayTarget.getParent());

            // 如果展示目录已有同名文件，先归档
            Path existingDisplayFile = displayTarget;
            if (Files.exists(existingDisplayFile)) {
                Files.createDirectories(archiveDir);
                Path archiveTarget = archiveDir.resolve(fileName);

                Files.move(existingDisplayFile, archiveTarget, StandardCopyOption.REPLACE_EXISTING);

                weatherFileScanRecordDao.update(
                        null,
                        new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<WeatherFileScanRecordEntity>()
                                .eq("display_path", existingDisplayFile.toString())
                                .set("status", Constant.FileScanStatus.ARCHIVED.getValue())
                                .set("archive_path", archiveTarget.toString())
                                .set("archived_at", new Date())
                );
            }

            Files.copy(filePath, displayTarget, StandardCopyOption.REPLACE_EXISTING);

            ParseResult parsed = FileNameParser.parse(fileName);

            WeatherFileScanRecordEntity entity = new WeatherFileScanRecordEntity();
            entity.setId(IdUtil.getSnowflakeNextId());
            entity.setFileName(fileName);
            entity.setFileExt(ext);
            entity.setFileSize(Files.size(filePath));
            entity.setMd5Hash(md5);

            if (deptId == null) {
                // 根目录文件：统一归为模式预报类别
                entity.setKeywordCategory("模式预报");
                entity.setKeywordRegion(parsed.getModelName()); // 已映射的有值，未映射的为 null
            } else {
                entity.setKeywordRegion(parsed.getRegion());
                entity.setKeywordCategory(parsed.getCategory());
            }
            entity.setKeywordPeriod(parsed.getPeriod());

            entity.setSrcPath(filePath.toString());
            entity.setDisplayPath(displayTarget.toString());
            entity.setStatus(Constant.FileScanStatus.DISPLAYED.getValue());
            entity.setFileCreateTime(fileCreateTime);
            entity.setScanTime(new Date());
            entity.setDeptId(deptId);
            entity.setCreator(0L);
            entity.setCreateDate(new Date());

            weatherFileScanRecordDao.insert(entity);
            log.info("文件扫描入库成功: {} (deptId={})", fileName, deptId);

        } catch (Exception e) {
            log.error("处理文件失败: {}", filePath, e);
        }
    }
}
