using System.IO;
using System.Text;
using System.Text.Json;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

namespace AxCopilot.Services.Agent;

/// <summary>XML 파싱, XPath 쿼리, 변환 도구.</summary>
public class XmlTool : IAgentTool
{
    public string Name => "xml_tool";
    public string Description =>
        "Parse and query XML documents. Actions: " +
        "'parse' — parse XML file/string and return structure summary; " +
        "'xpath' — evaluate XPath expression and return matching nodes; " +
        "'to_json' — convert XML to JSON; " +
        "'format' — pretty-print XML with indentation.";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["action"] = new()
            {
                Type = "string",
                Description = "Action: parse, xpath, to_json, format",
                Enum = ["parse", "xpath", "to_json", "format"],
            },
            ["path"] = new()
            {
                Type = "string",
                Description = "XML file path (optional if 'xml' is provided)",
            },
            ["xml"] = new()
            {
                Type = "string",
                Description = "XML string (optional if 'path' is provided)",
            },
            ["expression"] = new()
            {
                Type = "string",
                Description = "XPath expression (for 'xpath' action)",
            },
        },
        Required = ["action"],
    };

    public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
    {
        var action = args.GetProperty("action").GetString() ?? "";
        var xmlStr = args.TryGetProperty("xml", out var x) ? x.GetString() ?? "" : "";
        var rawPath = args.TryGetProperty("path", out var pv) ? pv.GetString() ?? "" : "";
        var expression = args.TryGetProperty("expression", out var ex) ? ex.GetString() ?? "" : "";

        try
        {
            // XML 소스 결정
            if (string.IsNullOrEmpty(xmlStr) && !string.IsNullOrEmpty(rawPath))
            {
                var path = Path.IsPathRooted(rawPath) ? rawPath : Path.Combine(context.WorkFolder, rawPath);
                if (!context.IsPathAllowed(path))
                    return Task.FromResult(ToolResult.Fail($"경로 접근 차단: {path}"));
                if (!File.Exists(path))
                    return Task.FromResult(ToolResult.Fail($"파일 없음: {path}"));
                xmlStr = File.ReadAllText(path);
            }

            if (string.IsNullOrEmpty(xmlStr))
                return Task.FromResult(ToolResult.Fail("'xml' 또는 'path' 중 하나를 지정해야 합니다."));

            var doc = XDocument.Parse(xmlStr);

            return action switch
            {
                "parse" => Task.FromResult(ParseSummary(doc)),
                "xpath" => Task.FromResult(EvalXPath(doc, expression)),
                "to_json" => Task.FromResult(XmlToJson(doc)),
                "format" => Task.FromResult(FormatXml(doc)),
                _ => Task.FromResult(ToolResult.Fail($"Unknown action: {action}")),
            };
        }
        catch (XmlException xe)
        {
            return Task.FromResult(ToolResult.Fail($"XML 파싱 오류: {xe.Message}"));
        }
        catch (Exception e)
        {
            return Task.FromResult(ToolResult.Fail($"XML 처리 오류: {e.Message}"));
        }
    }

    private static ToolResult ParseSummary(XDocument doc)
    {
        var sb = new StringBuilder();
        sb.AppendLine($"Root: {doc.Root?.Name.LocalName ?? "(none)"}");
        if (doc.Root != null)
        {
            var ns = doc.Root.Name.Namespace;
            if (!string.IsNullOrEmpty(ns.NamespaceName))
                sb.AppendLine($"Namespace: {ns.NamespaceName}");

            var elements = doc.Descendants().Count();
            var attrs = doc.Descendants().SelectMany(e => e.Attributes()).Count();
            sb.AppendLine($"Elements: {elements}");
            sb.AppendLine($"Attributes: {attrs}");

            // 최상위 자식 요소 나열 (최대 20개)
            var children = doc.Root.Elements().Take(20).ToList();
            sb.AppendLine($"Top-level children ({doc.Root.Elements().Count()}):");
            foreach (var child in children)
                sb.AppendLine($"  <{child.Name.LocalName}> ({child.Elements().Count()} children)");
        }
        return ToolResult.Ok(sb.ToString());
    }

    private static ToolResult EvalXPath(XDocument doc, string xpath)
    {
        if (string.IsNullOrEmpty(xpath))
            return ToolResult.Fail("XPath 'expression'이 필요합니다.");

        var results = doc.XPathSelectElements(xpath).Take(50).ToList();
        if (results.Count == 0)
            return ToolResult.Ok("매칭 노드 없음.");

        var sb = new StringBuilder();
        sb.AppendLine($"매칭: {results.Count}개 노드");
        foreach (var el in results)
        {
            var text = el.ToString();
            if (text.Length > 500) text = text[..500] + "...";
            sb.AppendLine(text);
        }
        return ToolResult.Ok(sb.ToString());
    }

    private static ToolResult XmlToJson(XDocument doc)
    {
        var json = System.Text.Json.JsonSerializer.Serialize(
            XmlToDict(doc.Root!),
            new JsonSerializerOptions { WriteIndented = true });
        if (json.Length > 50_000) json = json[..50_000] + "\n... (truncated)";
        return ToolResult.Ok(json);
    }

    private static Dictionary<string, object?> XmlToDict(XElement el)
    {
        var dict = new Dictionary<string, object?>();
        foreach (var attr in el.Attributes())
            dict[$"@{attr.Name.LocalName}"] = attr.Value;

        var groups = el.Elements().GroupBy(e => e.Name.LocalName).ToList();
        foreach (var g in groups)
        {
            var items = g.ToList();
            if (items.Count == 1)
            {
                var child = items[0];
                dict[g.Key] = child.HasElements ? XmlToDict(child) : (object?)child.Value;
            }
            else
            {
                dict[g.Key] = items.Select(c => c.HasElements ? (object)XmlToDict(c) : c.Value).ToList();
            }
        }

        if (!el.HasElements && groups.Count == 0 && !string.IsNullOrEmpty(el.Value))
            dict["#text"] = el.Value;

        return dict;
    }

    private static ToolResult FormatXml(XDocument doc)
    {
        var sb = new StringBuilder();
        using var writer = XmlWriter.Create(sb, new XmlWriterSettings
        {
            Indent = true,
            IndentChars = "  ",
            OmitXmlDeclaration = false,
        });
        doc.WriteTo(writer);
        writer.Flush();
        var result = sb.ToString();
        if (result.Length > 50_000) result = result[..50_000] + "\n... (truncated)";
        return ToolResult.Ok(result);
    }
}
