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

namespace AxCopilot.Services.Agent;

/// <summary>
/// 파일 압축(zip) / 해제 도구.
/// </summary>
public class ZipTool : IAgentTool
{
    public string Name => "zip_tool";
    public string Description =>
        "Compress or extract zip archives. Actions: " +
        "'compress' — create a zip file from files/folders; " +
        "'extract' — extract a zip file to a directory; " +
        "'list' — list contents of a zip file without extracting.";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["action"] = new()
            {
                Type = "string",
                Description = "Action to perform",
                Enum = ["compress", "extract", "list"],
            },
            ["zip_path"] = new()
            {
                Type = "string",
                Description = "Path to the zip file (to create or extract)",
            },
            ["source_path"] = new()
            {
                Type = "string",
                Description = "Source file or directory path (for compress action)",
            },
            ["dest_path"] = new()
            {
                Type = "string",
                Description = "Destination directory (for extract action)",
            },
        },
        Required = ["action", "zip_path"],
    };

    private const long MaxExtractSize = 500 * 1024 * 1024; // 500MB 제한

    public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
    {
        var action  = args.GetProperty("action").GetString() ?? "";
        var zipPath = args.GetProperty("zip_path").GetString() ?? "";

        if (!Path.IsPathRooted(zipPath))
            zipPath = Path.Combine(context.WorkFolder, zipPath);

        try
        {
            return Task.FromResult(action switch
            {
                "compress" => Compress(args, zipPath, context),
                "extract"  => Extract(args, zipPath, context),
                "list"     => ListContents(zipPath),
                _          => ToolResult.Fail($"Unknown action: {action}"),
            });
        }
        catch (Exception ex)
        {
            return Task.FromResult(ToolResult.Fail($"Zip 오류: {ex.Message}"));
        }
    }

    private static ToolResult Compress(JsonElement args, string zipPath, AgentContext context)
    {
        if (!args.TryGetProperty("source_path", out var sp))
            return ToolResult.Fail("'source_path' is required for compress action");

        var sourcePath = sp.GetString() ?? "";
        if (!Path.IsPathRooted(sourcePath))
            sourcePath = Path.Combine(context.WorkFolder, sourcePath);

        if (File.Exists(zipPath))
            return ToolResult.Fail($"Zip file already exists: {zipPath}");

        if (Directory.Exists(sourcePath))
        {
            ZipFile.CreateFromDirectory(sourcePath, zipPath, CompressionLevel.Optimal, includeBaseDirectory: false);
            var info = new FileInfo(zipPath);
            return ToolResult.Ok($"✓ Created {zipPath} ({info.Length / 1024}KB)", zipPath);
        }
        else if (File.Exists(sourcePath))
        {
            using var zip = ZipFile.Open(zipPath, ZipArchiveMode.Create);
            zip.CreateEntryFromFile(sourcePath, Path.GetFileName(sourcePath), CompressionLevel.Optimal);
            var info = new FileInfo(zipPath);
            return ToolResult.Ok($"✓ Created {zipPath} ({info.Length / 1024}KB)", zipPath);
        }
        else
        {
            return ToolResult.Fail($"Source not found: {sourcePath}");
        }
    }

    private static ToolResult Extract(JsonElement args, string zipPath, AgentContext context)
    {
        if (!File.Exists(zipPath))
            return ToolResult.Fail($"Zip file not found: {zipPath}");

        var destPath = args.TryGetProperty("dest_path", out var dp)
            ? dp.GetString() ?? "" : "";
        if (string.IsNullOrEmpty(destPath))
            destPath = Path.Combine(Path.GetDirectoryName(zipPath) ?? context.WorkFolder,
                Path.GetFileNameWithoutExtension(zipPath));
        if (!Path.IsPathRooted(destPath))
            destPath = Path.Combine(context.WorkFolder, destPath);

        // 사이즈 체크
        using (var check = ZipFile.OpenRead(zipPath))
        {
            var totalSize = check.Entries.Sum(e => e.Length);
            if (totalSize > MaxExtractSize)
                return ToolResult.Fail($"Uncompressed size ({totalSize / 1024 / 1024}MB) exceeds 500MB limit");

            // 보안: 상위 경로 이탈 방지
            foreach (var entry in check.Entries)
            {
                var fullPath = Path.GetFullPath(Path.Combine(destPath, entry.FullName));
                if (!fullPath.StartsWith(Path.GetFullPath(destPath), StringComparison.OrdinalIgnoreCase))
                    return ToolResult.Fail($"Security: entry '{entry.FullName}' escapes destination directory");
            }
        }

        Directory.CreateDirectory(destPath);
        ZipFile.ExtractToDirectory(zipPath, destPath, overwriteFiles: true);

        var fileCount = Directory.GetFiles(destPath, "*", SearchOption.AllDirectories).Length;
        return ToolResult.Ok($"✓ Extracted {fileCount} files to {destPath}");
    }

    private static ToolResult ListContents(string zipPath)
    {
        if (!File.Exists(zipPath))
            return ToolResult.Fail($"Zip file not found: {zipPath}");

        using var zip = ZipFile.OpenRead(zipPath);
        var sb = new System.Text.StringBuilder();
        sb.AppendLine($"Archive: {Path.GetFileName(zipPath)} ({new FileInfo(zipPath).Length / 1024}KB)");
        sb.AppendLine($"Entries: {zip.Entries.Count}");
        sb.AppendLine();
        sb.AppendLine($"{"Size",10}  {"Compressed",10}  {"Name"}");
        sb.AppendLine(new string('-', 60));

        var limit = Math.Min(zip.Entries.Count, 200);
        foreach (var entry in zip.Entries.Take(limit))
        {
            sb.AppendLine($"{entry.Length,10}  {entry.CompressedLength,10}  {entry.FullName}");
        }
        if (zip.Entries.Count > limit)
            sb.AppendLine($"\n... and {zip.Entries.Count - limit} more entries");

        var totalUncompressed = zip.Entries.Sum(e => e.Length);
        sb.AppendLine($"\nTotal uncompressed: {totalUncompressed / 1024}KB");

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