using System.Text.Json;
using System.Text.RegularExpressions;

namespace AxCopilot.Services.Agent;

/// <summary>
/// 정규식 테스트·추출·치환 도구.
/// 패턴 매칭, 그룹 추출, 치환, 패턴 설명 기능을 제공합니다.
/// </summary>
public class RegexTool : IAgentTool
{
    public string Name => "regex_tool";
    public string Description =>
        "Regular expression tool. Actions: " +
        "'test' — check if text matches a pattern; " +
        "'match' — find all matches with groups; " +
        "'replace' — replace matches with replacement string; " +
        "'split' — split text by pattern; " +
        "'extract' — extract named/numbered groups from first match.";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["action"] = new()
            {
                Type = "string",
                Description = "Action to perform",
                Enum = ["test", "match", "replace", "split", "extract"],
            },
            ["pattern"] = new()
            {
                Type = "string",
                Description = "Regular expression pattern",
            },
            ["text"] = new()
            {
                Type = "string",
                Description = "Text to process",
            },
            ["replacement"] = new()
            {
                Type = "string",
                Description = "Replacement string for replace action (supports $1, $2, ${name})",
            },
            ["flags"] = new()
            {
                Type = "string",
                Description = "Regex flags: 'i' (ignore case), 'm' (multiline), 's' (singleline). Combine: 'im'",
            },
        },
        Required = ["action", "pattern", "text"],
    };

    private const int MaxTimeout = 5000; // ReDoS 방지

    public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
    {
        var action      = args.GetProperty("action").GetString() ?? "";
        var pattern     = args.GetProperty("pattern").GetString() ?? "";
        var text        = args.GetProperty("text").GetString() ?? "";
        var replacement = args.TryGetProperty("replacement", out var r) ? r.GetString() ?? "" : "";
        var flags       = args.TryGetProperty("flags", out var f) ? f.GetString() ?? "" : "";

        try
        {
            var options = ParseFlags(flags);
            var regex = new Regex(pattern, options, TimeSpan.FromMilliseconds(MaxTimeout));

            return Task.FromResult(action switch
            {
                "test"    => Test(regex, text),
                "match"   => Match(regex, text),
                "replace" => Replace(regex, text, replacement),
                "split"   => Split(regex, text),
                "extract" => Extract(regex, text),
                _         => ToolResult.Fail($"Unknown action: {action}"),
            });
        }
        catch (RegexMatchTimeoutException)
        {
            return Task.FromResult(ToolResult.Fail("정규식 실행 시간 초과 (ReDoS 방지). 패턴을 간소화하세요."));
        }
        catch (Exception ex)
        {
            return Task.FromResult(ToolResult.Fail($"정규식 오류: {ex.Message}"));
        }
    }

    private static RegexOptions ParseFlags(string flags)
    {
        var options = RegexOptions.None;
        foreach (var c in flags)
        {
            options |= c switch
            {
                'i' => RegexOptions.IgnoreCase,
                'm' => RegexOptions.Multiline,
                's' => RegexOptions.Singleline,
                _   => RegexOptions.None,
            };
        }
        return options;
    }

    private static ToolResult Test(Regex regex, string text)
    {
        var isMatch = regex.IsMatch(text);
        return ToolResult.Ok(isMatch ? "✓ Pattern matches" : "✗ No match");
    }

    private static ToolResult Match(Regex regex, string text)
    {
        var matches = regex.Matches(text);
        if (matches.Count == 0)
            return ToolResult.Ok("No matches found.");

        var sb = new System.Text.StringBuilder();
        sb.AppendLine($"Found {matches.Count} match(es):");
        var limit = Math.Min(matches.Count, 50); // 최대 50개
        for (var i = 0; i < limit; i++)
        {
            var m = matches[i];
            sb.AppendLine($"\n[{i}] \"{Truncate(m.Value, 200)}\" (index {m.Index}, length {m.Length})");
            if (m.Groups.Count > 1)
            {
                for (var g = 1; g < m.Groups.Count; g++)
                {
                    var group = m.Groups[g];
                    var name = regex.GroupNameFromNumber(g);
                    var label = name != g.ToString() ? $"'{name}'" : $"${g}";
                    sb.AppendLine($"    Group {label}: \"{Truncate(group.Value, 100)}\"");
                }
            }
        }
        if (matches.Count > limit)
            sb.AppendLine($"\n... and {matches.Count - limit} more matches");

        return ToolResult.Ok(sb.ToString());
    }

    private static ToolResult Replace(Regex regex, string text, string replacement)
    {
        var result = regex.Replace(text, replacement);
        var count = regex.Matches(text).Count;
        if (result.Length > 8000)
            result = result[..8000] + "\n... (truncated)";
        return ToolResult.Ok($"Replaced {count} occurrence(s):\n\n{result}");
    }

    private static ToolResult Split(Regex regex, string text)
    {
        var parts = regex.Split(text);
        var sb = new System.Text.StringBuilder();
        sb.AppendLine($"Split into {parts.Length} parts:");
        var limit = Math.Min(parts.Length, 100);
        for (var i = 0; i < limit; i++)
            sb.AppendLine($"  [{i}] \"{Truncate(parts[i], 200)}\"");
        if (parts.Length > limit)
            sb.AppendLine($"\n... and {parts.Length - limit} more parts");
        return ToolResult.Ok(sb.ToString());
    }

    private static ToolResult Extract(Regex regex, string text)
    {
        var m = regex.Match(text);
        if (!m.Success)
            return ToolResult.Ok("No match found.");

        var sb = new System.Text.StringBuilder();
        sb.AppendLine($"Match: \"{Truncate(m.Value, 300)}\"");
        if (m.Groups.Count > 1)
        {
            sb.AppendLine("\nGroups:");
            for (var g = 1; g < m.Groups.Count; g++)
            {
                var group = m.Groups[g];
                var name = regex.GroupNameFromNumber(g);
                var label = name != g.ToString() ? $"'{name}'" : $"${g}";
                sb.AppendLine($"  {label}: \"{Truncate(group.Value, 200)}\"");
            }
        }
        return ToolResult.Ok(sb.ToString());
    }

    private static string Truncate(string s, int maxLen) =>
        s.Length <= maxLen ? s : s[..maxLen] + "…";
}
