using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace AxCopilot.Services.Agent;

/// <summary>
/// 프로젝트별 규칙 파일(.ax/rules/*.md)을 로드하고 컨텍스트에 맞는 규칙을 선별합니다.
/// 각 규칙 파일은 YAML 프론트매터로 적용 조건을 정의합니다.
/// </summary>
public static class ProjectRulesService
{
    /// <summary>파싱된 프로젝트 규칙.</summary>
    public class ProjectRule
    {
        /// <summary>규칙 파일 경로.</summary>
        public string FilePath { get; set; } = "";

        /// <summary>규칙 이름 (프론트매터 name 또는 파일명).</summary>
        public string Name { get; set; } = "";

        /// <summary>규칙 설명.</summary>
        public string Description { get; set; } = "";

        /// <summary>적용 대상 글로브 패턴. 예: "*.cs", "src/**/*.ts"</summary>
        public string AppliesTo { get; set; } = "";

        /// <summary>적용 시점. 예: "always", "code-review", "document", "refactor"</summary>
        public string When { get; set; } = "always";

        /// <summary>규칙 본문 (프론트매터 제외).</summary>
        public string Body { get; set; } = "";
    }

    // YAML 프론트매터 경계
    private static readonly Regex FrontMatterRegex = new(
        @"^---\s*\n(.*?)\n---\s*\n",
        RegexOptions.Singleline | RegexOptions.Compiled);

    // YAML 키-값 파싱 (단순 1줄 값만)
    private static readonly Regex YamlKeyValue = new(
        @"^\s*(\w[\w-]*)\s*:\s*(.+?)\s*$",
        RegexOptions.Multiline | RegexOptions.Compiled);

    /// <summary>
    /// 작업 폴더에서 .ax/rules/ 디렉토리의 모든 규칙을 로드합니다.
    /// 최대 3단계 상위 폴더까지 .ax/rules/ 를 탐색합니다.
    /// </summary>
    public static List<ProjectRule> LoadRules(string workFolder)
    {
        var rules = new List<ProjectRule>();
        if (string.IsNullOrEmpty(workFolder)) return rules;

        var rulesDir = FindRulesDirectory(workFolder);
        if (rulesDir == null) return rules;

        try
        {
            foreach (var file in Directory.GetFiles(rulesDir, "*.md"))
            {
                var rule = ParseRuleFile(file);
                if (rule != null)
                    rules.Add(rule);
            }
        }
        catch (Exception) { /* 디렉토리 읽기 실패 시 빈 목록 반환 */ }

        return rules;
    }

    /// <summary>
    /// 컨텍스트에 맞는 규칙만 필터링합니다.
    /// </summary>
    /// <param name="rules">전체 규칙 목록</param>
    /// <param name="when">현재 컨텍스트 (예: "code-review", "document", "always")</param>
    /// <param name="filePaths">현재 작업 대상 파일 경로들 (applies-to 매칭용)</param>
    public static List<ProjectRule> FilterRules(
        List<ProjectRule> rules, string when = "always", IEnumerable<string>? filePaths = null)
    {
        var result = new List<ProjectRule>();

        foreach (var rule in rules)
        {
            // when 조건 체크
            var ruleWhen = rule.When.ToLowerInvariant().Trim();
            if (ruleWhen != "always" && ruleWhen != when.ToLowerInvariant())
                continue;

            // applies-to 조건 체크
            if (!string.IsNullOrEmpty(rule.AppliesTo) && filePaths != null)
            {
                var pattern = rule.AppliesTo.Trim();
                if (!filePaths.Any(fp => MatchesGlob(fp, pattern)))
                    continue;
            }

            result.Add(rule);
        }

        return result;
    }

    /// <summary>
    /// 규칙 목록을 시스템 프롬프트용 텍스트로 포맷합니다.
    /// </summary>
    public static string FormatForSystemPrompt(List<ProjectRule> rules)
    {
        if (rules.Count == 0) return "";

        var sb = new StringBuilder();
        sb.AppendLine("\n## 프로젝트 규칙 (.ax/rules/)");
        sb.AppendLine("아래 규칙을 반드시 준수하세요:\n");

        foreach (var rule in rules)
        {
            if (!string.IsNullOrEmpty(rule.Name))
                sb.AppendLine($"### {rule.Name}");
            if (!string.IsNullOrEmpty(rule.Description))
                sb.AppendLine($"*{rule.Description}*\n");
            sb.AppendLine(rule.Body.Trim());
            sb.AppendLine();
        }

        return sb.ToString();
    }

    /// <summary>.ax/rules/ 디렉토리를 작업 폴더에서 최대 3단계 상위까지 탐색합니다.</summary>
    internal static string? FindRulesDirectory(string workFolder)
    {
        var dir = workFolder;
        for (int i = 0; i < 3; i++)
        {
            if (string.IsNullOrEmpty(dir)) break;
            var rulesPath = Path.Combine(dir, ".ax", "rules");
            if (Directory.Exists(rulesPath)) return rulesPath;
            dir = Directory.GetParent(dir)?.FullName;
        }
        return null;
    }

    /// <summary>규칙 파일을 파싱합니다 (YAML 프론트매터 + 본문).</summary>
    internal static ProjectRule? ParseRuleFile(string filePath)
    {
        try
        {
            var content = File.ReadAllText(filePath, Encoding.UTF8);
            if (string.IsNullOrWhiteSpace(content)) return null;

            var rule = new ProjectRule
            {
                FilePath = filePath,
                Name = Path.GetFileNameWithoutExtension(filePath),
            };

            // 프론트매터 파싱
            var fmMatch = FrontMatterRegex.Match(content);
            if (fmMatch.Success)
            {
                var yaml = fmMatch.Groups[1].Value;
                foreach (Match kv in YamlKeyValue.Matches(yaml))
                {
                    var key = kv.Groups[1].Value.ToLowerInvariant();
                    var val = kv.Groups[2].Value.Trim().Trim('"', '\'');

                    switch (key)
                    {
                        case "name": rule.Name = val; break;
                        case "description": rule.Description = val; break;
                        case "applies-to" or "appliesto": rule.AppliesTo = val; break;
                        case "when": rule.When = val; break;
                    }
                }

                rule.Body = content[(fmMatch.Index + fmMatch.Length)..];
            }
            else
            {
                rule.Body = content;
            }

            return string.IsNullOrWhiteSpace(rule.Body) ? null : rule;
        }
        catch (Exception)
        {
            return null;
        }
    }

    /// <summary>간단한 글로브 패턴 매칭 (*, ** 지원).</summary>
    private static bool MatchesGlob(string path, string pattern)
    {
        // "*.cs" → 확장자 매칭
        if (pattern.StartsWith("*."))
            return path.EndsWith(pattern[1..], StringComparison.OrdinalIgnoreCase);

        // "**/*.cs" → 경로 내 확장자 매칭
        if (pattern.StartsWith("**/"))
        {
            var subPattern = pattern[3..];
            return MatchesGlob(Path.GetFileName(path), subPattern);
        }

        // 정확한 파일명 매칭
        return Path.GetFileName(path).Equals(pattern, StringComparison.OrdinalIgnoreCase);
    }
}
