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

namespace AxCopilot.Services.Agent;

/// <summary>파일 인코딩 감지 및 변환 도구.</summary>
public class EncodingTool : IAgentTool
{
    public string Name => "encoding_tool";
    public string Description =>
        "Detect and convert file text encoding. Actions: " +
        "'detect' — detect file encoding (UTF-8, EUC-KR, etc.); " +
        "'convert' — convert file from one encoding to another; " +
        "'list' — list common encoding names.";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["action"] = new()
            {
                Type = "string",
                Description = "Action: detect, convert, list",
                Enum = ["detect", "convert", "list"],
            },
            ["path"] = new()
            {
                Type = "string",
                Description = "File path",
            },
            ["from_encoding"] = new()
            {
                Type = "string",
                Description = "Source encoding name (e.g. 'euc-kr', 'shift-jis'). Auto-detected if omitted.",
            },
            ["to_encoding"] = new()
            {
                Type = "string",
                Description = "Target encoding name (default: 'utf-8')",
            },
        },
        Required = ["action"],
    };

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

        if (action == "list")
            return ListEncodings();

        var rawPath = args.TryGetProperty("path", out var pv) ? pv.GetString() ?? "" : "";
        if (string.IsNullOrEmpty(rawPath))
            return ToolResult.Fail("'path'가 필요합니다.");

        var path = Path.IsPathRooted(rawPath) ? rawPath : Path.Combine(context.WorkFolder, rawPath);
        if (!context.IsPathAllowed(path))
            return ToolResult.Fail($"경로 접근 차단: {path}");
        if (!File.Exists(path))
            return ToolResult.Fail($"파일 없음: {path}");

        try
        {
            return action switch
            {
                "detect" => DetectEncoding(path),
                "convert" => await ConvertEncoding(path, args, context),
                _ => ToolResult.Fail($"Unknown action: {action}"),
            };
        }
        catch (Exception ex)
        {
            return ToolResult.Fail($"인코딩 처리 오류: {ex.Message}");
        }
    }

    private static ToolResult DetectEncoding(string path)
    {
        var bytes = File.ReadAllBytes(path);
        var detected = DetectEncodingFromBytes(bytes);
        var sb = new StringBuilder();
        sb.AppendLine($"File: {Path.GetFileName(path)}");
        sb.AppendLine($"Size: {bytes.Length:N0} bytes");
        sb.AppendLine($"Detected Encoding: {detected.EncodingName}");
        sb.AppendLine($"Code Page: {detected.CodePage}");
        sb.AppendLine($"BOM Present: {HasBom(bytes)}");
        return ToolResult.Ok(sb.ToString());
    }

    private static async Task<ToolResult> ConvertEncoding(string path, JsonElement args, AgentContext context)
    {
        var toName = args.TryGetProperty("to_encoding", out var te) ? te.GetString() ?? "utf-8" : "utf-8";

        // 쓰기 권한 확인
        var allowed = await context.CheckWritePermissionAsync("encoding_tool", path);
        if (!allowed) return ToolResult.Fail("파일 쓰기 권한이 거부되었습니다.");

        // 소스 인코딩 결정
        Encoding fromEnc;
        if (args.TryGetProperty("from_encoding", out var fe) && !string.IsNullOrEmpty(fe.GetString()))
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            fromEnc = Encoding.GetEncoding(fe.GetString()!);
        }
        else
        {
            var rawBytes = File.ReadAllBytes(path);
            fromEnc = DetectEncodingFromBytes(rawBytes);
        }

        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        var toEnc = Encoding.GetEncoding(toName);

        var content = File.ReadAllText(path, fromEnc);
        File.WriteAllText(path, content, toEnc);

        return ToolResult.Ok(
            $"변환 완료: {fromEnc.EncodingName} → {toEnc.EncodingName}\nFile: {path}",
            filePath: path);
    }

    private static ToolResult ListEncodings()
    {
        var sb = new StringBuilder();
        sb.AppendLine("주요 인코딩 목록:");
        sb.AppendLine("  utf-8       — UTF-8 (유니코드, 기본)");
        sb.AppendLine("  utf-16      — UTF-16 LE");
        sb.AppendLine("  utf-16BE    — UTF-16 BE");
        sb.AppendLine("  euc-kr      — EUC-KR (한국어)");
        sb.AppendLine("  ks_c_5601-1987 — 한글 완성형");
        sb.AppendLine("  shift_jis   — Shift-JIS (일본어)");
        sb.AppendLine("  gb2312      — GB2312 (중국어 간체)");
        sb.AppendLine("  iso-8859-1  — Latin-1 (서유럽)");
        sb.AppendLine("  ascii       — US-ASCII");
        sb.AppendLine("  utf-32      — UTF-32");
        return ToolResult.Ok(sb.ToString());
    }

    private static Encoding DetectEncodingFromBytes(byte[] bytes)
    {
        // BOM 감지
        if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
            return Encoding.UTF8;
        if (bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE)
            return Encoding.Unicode; // UTF-16 LE
        if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF)
            return Encoding.BigEndianUnicode;

        // 간단한 UTF-8 유효성 검사
        if (IsValidUtf8(bytes))
            return Encoding.UTF8;

        // 한국어 환경 기본값
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        try { return Encoding.GetEncoding("euc-kr"); }
        catch (Exception) { return Encoding.Default; }
    }

    private static bool IsValidUtf8(byte[] bytes)
    {
        var i = 0;
        var hasMultibyte = false;
        while (i < bytes.Length)
        {
            if (bytes[i] <= 0x7F) { i++; continue; }
            int extra;
            if ((bytes[i] & 0xE0) == 0xC0) extra = 1;
            else if ((bytes[i] & 0xF0) == 0xE0) extra = 2;
            else if ((bytes[i] & 0xF8) == 0xF0) extra = 3;
            else return false;

            if (i + extra >= bytes.Length) return false;
            for (var j = 1; j <= extra; j++)
                if ((bytes[i + j] & 0xC0) != 0x80) return false;
            hasMultibyte = true;
            i += extra + 1;
        }
        return hasMultibyte || bytes.Length < 100; // 순수 ASCII도 UTF-8으로 간주
    }

    private static bool HasBom(byte[] bytes)
    {
        if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) return true;
        if (bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) return true;
        if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) return true;
        return false;
    }
}
