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

namespace AxCopilot.Services.Agent;

/// <summary>
/// 두 텍스트 또는 파일을 비교하여 통합 diff 결과를 출력하는 도구.
/// </summary>
public class DiffTool : IAgentTool
{
    public string Name => "diff_tool";
    public string Description =>
        "Compare two texts or files and output a unified diff. " +
        "Use 'text' mode to compare two text strings directly, " +
        "or 'file' mode to compare two files by path.";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["mode"] = new()
            {
                Type = "string",
                Description = "Comparison mode",
                Enum = ["text", "file"],
            },
            ["left"] = new()
            {
                Type = "string",
                Description = "Left text content or file path",
            },
            ["right"] = new()
            {
                Type = "string",
                Description = "Right text content or file path",
            },
            ["left_label"] = new()
            {
                Type = "string",
                Description = "Label for left side (optional, default: 'left')",
            },
            ["right_label"] = new()
            {
                Type = "string",
                Description = "Label for right side (optional, default: 'right')",
            },
        },
        Required = ["mode", "left", "right"],
    };

    public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
    {
        var mode  = args.GetProperty("mode").GetString() ?? "text";
        var left  = args.GetProperty("left").GetString() ?? "";
        var right = args.GetProperty("right").GetString() ?? "";
        var leftLabel  = args.TryGetProperty("left_label", out var ll) ? ll.GetString() ?? "left" : "left";
        var rightLabel = args.TryGetProperty("right_label", out var rl) ? rl.GetString() ?? "right" : "right";

        try
        {
            if (mode == "file")
            {
                var leftPath  = Path.IsPathRooted(left) ? left : Path.Combine(context.WorkFolder, left);
                var rightPath = Path.IsPathRooted(right) ? right : Path.Combine(context.WorkFolder, right);

                if (!File.Exists(leftPath))  return Task.FromResult(ToolResult.Fail($"Left file not found: {leftPath}"));
                if (!File.Exists(rightPath)) return Task.FromResult(ToolResult.Fail($"Right file not found: {rightPath}"));

                left  = File.ReadAllText(leftPath);
                right = File.ReadAllText(rightPath);
                leftLabel  = Path.GetFileName(leftPath);
                rightLabel = Path.GetFileName(rightPath);
            }

            var leftLines  = left.Split('\n').Select(l => l.TrimEnd('\r')).ToArray();
            var rightLines = right.Split('\n').Select(l => l.TrimEnd('\r')).ToArray();

            var diff = GenerateUnifiedDiff(leftLines, rightLines, leftLabel, rightLabel);
            if (string.IsNullOrEmpty(diff))
                return Task.FromResult(ToolResult.Ok("No differences found — files/texts are identical."));

            if (diff.Length > 10000) diff = diff[..10000] + "\n... (truncated)";
            return Task.FromResult(ToolResult.Ok(diff));
        }
        catch (Exception ex)
        {
            return Task.FromResult(ToolResult.Fail($"Diff 오류: {ex.Message}"));
        }
    }

    private static string GenerateUnifiedDiff(string[] left, string[] right, string leftLabel, string rightLabel)
    {
        // 간단한 LCS 기반 diff
        var lcs = ComputeLcs(left, right);
        var sb = new System.Text.StringBuilder();
        sb.AppendLine($"--- {leftLabel}");
        sb.AppendLine($"+++ {rightLabel}");

        int li = 0, ri = 0, ci = 0;
        var hunks = new List<(int ls, int le, int rs, int re)>();

        // hunk 수집
        while (li < left.Length || ri < right.Length)
        {
            if (ci < lcs.Count && li < left.Length && ri < right.Length && left[li] == lcs[ci] && right[ri] == lcs[ci])
            {
                li++; ri++; ci++;
            }
            else
            {
                var hls = li; var hrs = ri;
                while (li < left.Length && (ci >= lcs.Count || left[li] != lcs[ci]))
                    li++;
                while (ri < right.Length && (ci >= lcs.Count || right[ri] != lcs[ci]))
                    ri++;
                hunks.Add((hls, li, hrs, ri));
            }
        }

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

        // 출력
        foreach (var (ls, le, rs, re) in hunks)
        {
            var contextStart = Math.Max(0, ls - 3);
            sb.AppendLine($"@@ -{ls + 1},{le - ls} +{rs + 1},{re - rs} @@");
            for (var i = ls; i < le; i++)
                sb.AppendLine($"-{left[i]}");
            for (var i = rs; i < re; i++)
                sb.AppendLine($"+{right[i]}");
        }

        return sb.ToString();
    }

    private static List<string> ComputeLcs(string[] a, string[] b)
    {
        var m = a.Length; var n = b.Length;
        // 메모리 절약을 위해 큰 파일은 제한
        if ((long)m * n > 10_000_000)
            return new List<string>(); // 너무 큰 경우 전체를 diff로 표시

        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;
    }
}
