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

namespace AxCopilot.Services.Agent;

/// <summary>
/// 파일 변경 사항을 미리 보여주고 사용자 승인을 받는 도구.
/// 통합 diff를 생성하여 "[PREVIEW_PENDING]" 접두사와 함께 반환합니다.
/// </summary>
public class DiffPreviewTool : IAgentTool
{
    public string Name => "diff_preview";

    public string Description =>
        "Preview file changes before applying them. Shows a unified diff and waits for user approval. " +
        "If approved, writes the new content to the file. If rejected, no changes are made. " +
        "The diff output is prefixed with [PREVIEW_PENDING] so the UI can show an approval panel.";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["path"] = new()
            {
                Type = "string",
                Description = "File path to modify",
            },
            ["new_content"] = new()
            {
                Type = "string",
                Description = "Proposed new content for the file",
            },
            ["description"] = new()
            {
                Type = "string",
                Description = "Description of the changes (optional)",
            },
        },
        Required = ["path", "new_content"],
    };

    public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
    {
        var rawPath = args.GetProperty("path").GetString() ?? "";
        var newContent = args.GetProperty("new_content").GetString() ?? "";
        var description = args.TryGetProperty("description", out var d) ? d.GetString() ?? "" : "";

        var path = Path.IsPathRooted(rawPath) ? rawPath : Path.Combine(context.WorkFolder, rawPath);

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

        try
        {
            // 원본 파일 읽기 (없으면 새 파일 생성으로 처리)
            var originalContent = "";
            var isNewFile = !File.Exists(path);
            if (!isNewFile)
                originalContent = await File.ReadAllTextAsync(path, ct);

            // 통합 diff 생성
            var originalLines = originalContent.Split('\n').Select(l => l.TrimEnd('\r')).ToArray();
            var newLines = newContent.Split('\n').Select(l => l.TrimEnd('\r')).ToArray();
            var diff = GenerateUnifiedDiff(originalLines, newLines, path);

            var sb = new StringBuilder();
            sb.AppendLine("[PREVIEW_PENDING]");

            if (!string.IsNullOrEmpty(description))
                sb.AppendLine($"변경 설명: {description}");

            sb.AppendLine($"파일: {path}");
            sb.AppendLine(isNewFile ? "상태: 새 파일 생성" : "상태: 기존 파일 수정");
            sb.AppendLine();

            if (string.IsNullOrEmpty(diff))
                sb.AppendLine("변경 사항 없음 — 내용이 동일합니다.");
            else
                sb.Append(diff);

            // 쓰기 권한 확인 (AskPermission 콜백 사용 — CustomMessageBox)
            if (!await context.CheckWritePermissionAsync("diff_preview", path))
                return ToolResult.Ok($"사용자가 파일 변경을 거부했습니다.\n\n{sb}");

            // 디렉토리 생성
            var dir = Path.GetDirectoryName(path);
            if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
                Directory.CreateDirectory(dir);

            await File.WriteAllTextAsync(path, newContent, ct);
            return ToolResult.Ok($"변경 사항이 적용되었습니다: {path}\n\n{sb}", path);
        }
        catch (Exception ex)
        {
            return ToolResult.Fail($"미리보기 오류: {ex.Message}");
        }
    }

    private static string GenerateUnifiedDiff(string[] original, string[] modified, string filePath)
    {
        var sb = new StringBuilder();
        sb.AppendLine($"--- {filePath} (원본)");
        sb.AppendLine($"+++ {filePath} (수정)");

        // 간단한 LCS 기반 diff
        var lcs = ComputeLcs(original, modified);
        int oi = 0, mi = 0, ci = 0;
        var hunks = new List<(int os, int oe, int ms, int me)>();

        while (oi < original.Length || mi < modified.Length)
        {
            if (ci < lcs.Count && oi < original.Length && mi < modified.Length
                && original[oi] == lcs[ci] && modified[mi] == lcs[ci])
            {
                oi++; mi++; ci++;
            }
            else
            {
                var hos = oi;
                var hms = mi;
                while (oi < original.Length && (ci >= lcs.Count || original[oi] != lcs[ci]))
                    oi++;
                while (mi < modified.Length && (ci >= lcs.Count || modified[mi] != lcs[ci]))
                    mi++;
                hunks.Add((hos, oi, hms, mi));
            }
        }

        if (hunks.Count == 0) return "";

        foreach (var (os, oe, ms, me) in hunks)
        {
            sb.AppendLine($"@@ -{os + 1},{oe - os} +{ms + 1},{me - ms} @@");
            for (var i = os; i < oe; i++)
                sb.AppendLine($"-{original[i]}");
            for (var i = ms; i < me; i++)
                sb.AppendLine($"+{modified[i]}");
        }

        return sb.ToString();
    }

    private static List<string> ComputeLcs(string[] a, string[] b)
    {
        var m = a.Length;
        var n = b.Length;

        // 메모리 절약: 큰 파일은 전체를 diff로 표시
        if ((long)m * n > 10_000_000)
            return [];

        var dp = new int[m + 1, n + 1];
        for (var i = m - 1; i >= 0; i--)
            for (var j = n - 1; j >= 0; j--)
                dp[i, j] = a[i] == b[j] ? dp[i + 1, j + 1] + 1 : Math.Max(dp[i + 1, j], dp[i, j + 1]);

        var result = new List<string>();
        int x = 0, y = 0;
        while (x < m && y < n)
        {
            if (a[x] == b[y]) { result.Add(a[x]); x++; y++; }
            else if (dp[x + 1, y] >= dp[x, y + 1]) x++;
            else y++;
        }
        return result;
    }
}
