package com.agent.management.engine;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

/**
 * .agentignore 文件解析器（仿 .gitignore）。
 *
 * 用于过滤 Hermes 工作目录文件列表（_workingDirFiles）和下载 zip 打包，
 * 避免 .hermes/skills 副本等系统内部文件泄露给前端或打包到下载产物。
 *
 * 支持的语法（.gitignore 子集）：
 *   # 注释      行首 # 视为注释
 *   空行        跳过
 *   /foo        锚定到工作目录根
 *   foo         任意层级匹配（相当于多层级 glob）
 *   foo/        仅匹配目录
 *   星号/问号   通配符（不含路径分隔符）
 * 不支持：! 反向匹配、双星号跨层通配
 */
@Slf4j
@Component
public class AgentIgnoreFilter {

    /** 文件不存在时启用的内置兜底规则，至少要忽略 .hermes 内部目录 */
    private static final String[] DEFAULT_RULES = {
            ".hermes/",
            "*.log",
            "*.tmp",
            ".DS_Store",
            "Thumbs.db"
    };

    private final Path ignoreFile;
    private volatile List<Rule> rules = Collections.emptyList();

    public AgentIgnoreFilter(@Value("${workflow.agent-ignore-file:./.agentignore}") String path) {
        this.ignoreFile = Paths.get(path).toAbsolutePath().normalize();
    }

    @PostConstruct
    public void init() {
        reload();
    }

    /** 重新加载规则（运行时修改 .agentignore 后可调用） */
    public void reload() {
        if (!Files.exists(ignoreFile)) {
            rules = compile(Arrays.asList(DEFAULT_RULES));
            log.info("[AgentIgnoreFilter] 文件不存在 {}，启用内置默认规则（{} 条）", ignoreFile, rules.size());
            return;
        }
        try {
            List<String> lines = Files.readAllLines(ignoreFile);
            rules = compile(lines);
            log.info("[AgentIgnoreFilter] 已加载 {} 条规则: {}", rules.size(), ignoreFile);
        } catch (IOException e) {
            log.warn("[AgentIgnoreFilter] 加载失败: {} err={}", ignoreFile, e.getMessage());
            rules = compile(Arrays.asList(DEFAULT_RULES));
        }
    }

    private List<Rule> compile(List<String> lines) {
        List<Rule> compiled = new ArrayList<>();
        for (String line : lines) {
            String trimmed = line.trim();
            if (trimmed.isEmpty() || trimmed.startsWith("#")) continue;
            Rule rule = Rule.compile(trimmed);
            if (rule != null) compiled.add(rule);
        }
        return Collections.unmodifiableList(compiled);
    }

    /**
     * 判断相对路径是否应该被忽略。
     *
     * @param relativePath 相对工作目录的路径（正反斜杠均可）
     * @param isDirectory  是否目录
     */
    public boolean shouldIgnore(String relativePath, boolean isDirectory) {
        if (relativePath == null || relativePath.isEmpty()) return false;
        String normalized = relativePath.replace('\\', '/');
        for (Rule rule : rules) {
            if (rule.matches(normalized, isDirectory)) return true;
        }
        return false;
    }

    /** 一条 .agentignore 规则 */
    static final class Rule {
        final boolean anchored;  // /foo 锚定根
        final boolean dirOnly;   // foo/ 仅目录
        final Pattern regex;

        private Rule(boolean anchored, boolean dirOnly, Pattern regex) {
            this.anchored = anchored;
            this.dirOnly = dirOnly;
            this.regex = regex;
        }

        static Rule compile(String raw) {
            boolean anchored = raw.startsWith("/");
            if (anchored) raw = raw.substring(1);
            boolean dirOnly = raw.endsWith("/");
            if (dirOnly) raw = raw.substring(0, raw.length() - 1);
            if (raw.isEmpty()) return null;

            StringBuilder sb = new StringBuilder();
            if (!anchored) sb.append("(?:.*/)?");  // 任意层级：可选前缀
            for (int i = 0; i < raw.length(); i++) {
                char c = raw.charAt(i);
                switch (c) {
                    case '*': sb.append("[^/]*"); break;
                    case '?': sb.append("[^/]"); break;
                    default:
                        if ("\\.[]{}()^$|+".indexOf(c) >= 0) {
                            sb.append("\\").append(c);
                        } else {
                            sb.append(c);
                        }
                }
            }
            return new Rule(anchored, dirOnly, Pattern.compile(sb.toString()));
        }

        boolean matches(String normalizedPath, boolean isDirectory) {
            if (dirOnly && !isDirectory) return false;
            return regex.matcher(normalizedPath).matches();
        }
    }
}
