using System.IO;
using System.Text;
using System.Text.Json;

namespace AxCopilot.Services.Agent;

/// <summary>파일의 특정 부분을 수정하는 도구 (old_string → new_string 패턴).</summary>
public class FileEditTool : IAgentTool
{
    public string Name => "file_edit";
    public string Description => "Edit a file by replacing an exact string match. Set replace_all=true to replace all occurrences; otherwise old_string must be unique.";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["path"] = new() { Type = "string", Description = "File path to edit" },
            ["old_string"] = new() { Type = "string", Description = "Exact string to find and replace" },
            ["new_string"] = new() { Type = "string", Description = "Replacement string" },
            ["replace_all"] = new() { Type = "boolean", Description = "Replace all occurrences (default false). If false, old_string must be unique." },
        },
        Required = ["path", "old_string", "new_string"]
    };

    public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct)
    {
        var path = args.GetProperty("path").GetString() ?? "";
        var oldStr = args.GetProperty("old_string").GetString() ?? "";
        var newStr = args.GetProperty("new_string").GetString() ?? "";
        var replaceAll = args.TryGetProperty("replace_all", out var ra) && ra.GetBoolean();

        var fullPath = FileReadTool.ResolvePath(path, context.WorkFolder);

        if (!context.IsPathAllowed(fullPath))
            return ToolResult.Fail($"경로 접근 차단: {fullPath}");

        if (!File.Exists(fullPath))
            return ToolResult.Fail($"파일이 존재하지 않습니다: {fullPath}");

        if (!await context.CheckWritePermissionAsync(Name, fullPath))
            return ToolResult.Fail($"쓰기 권한 거부: {fullPath}");

        try
        {
            var content = await File.ReadAllTextAsync(fullPath, Encoding.UTF8, ct);

            var count = CountOccurrences(content, oldStr);
            if (count == 0)
                return ToolResult.Fail($"old_string을 파일에서 찾을 수 없습니다.");
            if (!replaceAll && count > 1)
                return ToolResult.Fail($"old_string이 {count}번 발견됩니다. replace_all=true로 전체 교체하거나, 고유한 문자열을 지정하세요.");

            // Diff Preview: 변경 내용을 컨텍스트와 함께 표시
            var diffPreview = GenerateDiff(content, oldStr, newStr, fullPath);

            var updated = content.Replace(oldStr, newStr);
            await File.WriteAllTextAsync(fullPath, updated, Encoding.UTF8, ct);

            var msg = replaceAll && count > 1
                ? $"파일 수정 완료: {fullPath} ({count}곳 전체 교체)"
                : $"파일 수정 완료: {fullPath}";
            return ToolResult.Ok($"{msg}\n\n{diffPreview}", fullPath);
        }
        catch (Exception ex)
        {
            return ToolResult.Fail($"파일 수정 실패: {ex.Message}");
        }
    }

    /// <summary>변경 전/후 diff를 생성합니다 (unified diff 스타일).</summary>
    private static string GenerateDiff(string content, string oldStr, string newStr, string filePath)
    {
        var lines = content.Split('\n');
        var matchIdx = content.IndexOf(oldStr, StringComparison.Ordinal);
        if (matchIdx < 0) return "";

        // 변경 시작 줄 번호 계산
        var startLine = content[..matchIdx].Count(c => c == '\n');
        var oldLines = oldStr.Split('\n');
        var newLines = newStr.Split('\n');

        var sb = new StringBuilder();
        var fileName = Path.GetFileName(filePath);
        sb.AppendLine($"--- {fileName} (before)");
        sb.AppendLine($"+++ {fileName} (after)");

        // 컨텍스트 라인 수
        const int ctx = 2;
        var ctxStart = Math.Max(0, startLine - ctx);
        var ctxEnd = Math.Min(lines.Length - 1, startLine + oldLines.Length - 1 + ctx);

        sb.AppendLine($"@@ -{ctxStart + 1},{ctxEnd - ctxStart + 1} @@");

        // 앞쪽 컨텍스트
        for (int i = ctxStart; i < startLine; i++)
            sb.AppendLine($" {lines[i].TrimEnd('\r')}");

        // 삭제 라인
        foreach (var line in oldLines)
            sb.AppendLine($"-{line.TrimEnd('\r')}");

        // 추가 라인
        foreach (var line in newLines)
            sb.AppendLine($"+{line.TrimEnd('\r')}");

        // 뒤쪽 컨텍스트
        var afterEnd = startLine + oldLines.Length;
        for (int i = afterEnd; i <= ctxEnd && i < lines.Length; i++)
            sb.AppendLine($" {lines[i].TrimEnd('\r')}");

        return sb.ToString().TrimEnd();
    }

    private static int CountOccurrences(string text, string search)
    {
        if (string.IsNullOrEmpty(search)) return 0;
        int count = 0, idx = 0;
        while ((idx = text.IndexOf(search, idx, StringComparison.Ordinal)) != -1)
        {
            count++;
            idx += search.Length;
        }
        return count;
    }
}
