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

namespace AxCopilot.Services.Agent;

/// <summary>
/// C#/Python/JavaScript 코드 스니펫을 즉시 실행하는 도구.
/// 임시 파일에 코드를 저장하고 해당 런타임으로 실행합니다.
/// </summary>
public class SnippetRunnerTool : IAgentTool
{
    public string Name => "snippet_runner";
    public string Description =>
        "Execute a code snippet in C#, Python, or JavaScript. " +
        "Writes the code to a temp file, runs it, and returns stdout/stderr. " +
        "Useful for quick calculations, data transformations, format conversions, and testing algorithms. " +
        "Max execution time: 30 seconds. Max output: 8000 chars.";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["language"] = new()
            {
                Type = "string",
                Description = "Programming language: 'csharp', 'python', or 'javascript'",
                Enum = ["csharp", "python", "javascript"],
            },
            ["code"] = new()
            {
                Type = "string",
                Description = "Source code to execute. For C#: top-level statements or full Program class. " +
                              "For Python: standard script. For JavaScript: Node.js script.",
            },
            ["timeout"] = new()
            {
                Type = "integer",
                Description = "Timeout in seconds. Default: 30, max: 60.",
            },
        },
        Required = ["language", "code"],
    };

    // 위험한 코드 패턴 차단
    private static readonly string[] DangerousPatterns =
    [
        "Process.Start", "ProcessStartInfo",
        "Registry.", "RegistryKey",
        "Environment.Exit",
        "File.Delete", "Directory.Delete",
        "Format-Volume", "Remove-Item",
        "os.remove", "os.rmdir", "shutil.rmtree",
        "subprocess.call", "subprocess.Popen",
        "child_process", "require('fs').unlink",
        "exec(", "eval(",
    ];

    public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
    {
        var language = args.GetProperty("language").GetString() ?? "";
        var code = args.TryGetProperty("code", out var c) ? c.GetString() ?? "" : "";
        var timeout = args.TryGetProperty("timeout", out var to) ? Math.Min(to.GetInt32(), 60) : 30;

        if (string.IsNullOrWhiteSpace(code))
            return ToolResult.Fail("code가 비어 있습니다.");

        // 위험 패턴 검사
        foreach (var pattern in DangerousPatterns)
        {
            if (code.Contains(pattern, StringComparison.OrdinalIgnoreCase))
                return ToolResult.Fail($"보안 차단: '{pattern}' 패턴은 snippet_runner에서 허용되지 않습니다. process 도구를 사용하세요.");
        }

        // 권한 확인
        if (!await context.CheckWritePermissionAsync(Name, $"[{language}] {(code.Length > 100 ? code[..100] + "..." : code)}"))
            return ToolResult.Fail("코드 실행 권한 거부");

        // 설정에서 비활성화 여부 확인
        var enabled = context.Llm?.Code?.EnableSnippetRunner ?? true;
        if (!enabled)
            return ToolResult.Fail("snippet_runner가 설정에서 비활성화되어 있습니다. 설정 → AX Agent → 기능에서 활성화하세요.");

        return language switch
        {
            "csharp" => await RunCSharpAsync(code, timeout, context, ct),
            "python" => await RunScriptAsync("python", ".py", code, timeout, context, ct),
            "javascript" => await RunScriptAsync("node", ".js", code, timeout, context, ct),
            _ => ToolResult.Fail($"지원하지 않는 언어: {language}. csharp, python, javascript 중 선택하세요."),
        };
    }

    private async Task<ToolResult> RunCSharpAsync(string code, int timeout, AgentContext context, CancellationToken ct)
    {
        // dotnet-script 사용 시도, 없으면 dotnet run 임시 프로젝트
        var tempDir = Path.Combine(Path.GetTempPath(), $"axcopilot_snippet_{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempDir);

        try
        {
            // dotnet-script 먼저 확인
            if (await IsToolAvailableAsync("dotnet-script"))
            {
                var scriptFile = Path.Combine(tempDir, "snippet.csx");
                await File.WriteAllTextAsync(scriptFile, code, ct);
                return await RunProcessAsync("dotnet-script", scriptFile, timeout, tempDir, ct);
            }

            // dotnet run으로 폴백 — 임시 콘솔 프로젝트 생성
            var csprojContent = """
                <Project Sdk="Microsoft.NET.Sdk">
                  <PropertyGroup>
                    <OutputType>Exe</OutputType>
                    <TargetFramework>net8.0</TargetFramework>
                    <ImplicitUsings>enable</ImplicitUsings>
                  </PropertyGroup>
                </Project>
                """;
            await File.WriteAllTextAsync(Path.Combine(tempDir, "Snippet.csproj"), csprojContent, ct);

            // top-level statements 코드를 Program.cs로
            var programCode = code.Contains("class ") && code.Contains("static void Main")
                ? code  // 이미 전체 클래스
                : code; // top-level statements (net8.0 지원)
            await File.WriteAllTextAsync(Path.Combine(tempDir, "Program.cs"), programCode, ct);

            return await RunProcessAsync("dotnet", $"run --project \"{tempDir}\"", timeout, tempDir, ct);
        }
        finally
        {
            // 정리
            try { Directory.Delete(tempDir, true); } catch (Exception) { }
        }
    }

    private async Task<ToolResult> RunScriptAsync(string runtime, string extension, string code,
        int timeout, AgentContext context, CancellationToken ct)
    {
        // 런타임 사용 가능 확인
        if (!await IsToolAvailableAsync(runtime))
            return ToolResult.Fail($"{runtime}이 설치되지 않았습니다. 시스템에 {runtime}을 설치하세요.");

        var tempFile = Path.Combine(Path.GetTempPath(), $"axcopilot_snippet_{Guid.NewGuid():N}{extension}");
        try
        {
            await File.WriteAllTextAsync(tempFile, code, Encoding.UTF8, ct);
            return await RunProcessAsync(runtime, $"\"{tempFile}\"", timeout, context.WorkFolder, ct);
        }
        finally
        {
            try { File.Delete(tempFile); } catch (Exception) { }
        }
    }

    private static async Task<ToolResult> RunProcessAsync(string fileName, string arguments,
        int timeout, string workDir, CancellationToken ct)
    {
        var psi = new ProcessStartInfo
        {
            FileName = fileName,
            Arguments = arguments,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,
            StandardOutputEncoding = Encoding.UTF8,
            StandardErrorEncoding = Encoding.UTF8,
        };

        if (!string.IsNullOrEmpty(workDir) && Directory.Exists(workDir))
            psi.WorkingDirectory = workDir;

        using var process = new Process { StartInfo = psi };
        var stdout = new StringBuilder();
        var stderr = new StringBuilder();

        process.OutputDataReceived += (_, e) => { if (e.Data != null) stdout.AppendLine(e.Data); };
        process.ErrorDataReceived += (_, e) => { if (e.Data != null) stderr.AppendLine(e.Data); };

        try
        {
            process.Start();
        }
        catch (Exception ex)
        {
            return ToolResult.Fail($"실행 실패: {ex.Message}");
        }

        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
        cts.CancelAfter(TimeSpan.FromSeconds(timeout));

        try
        {
            await process.WaitForExitAsync(cts.Token);
        }
        catch (OperationCanceledException)
        {
            try { process.Kill(entireProcessTree: true); } catch (Exception) { }
            return ToolResult.Fail($"실행 타임아웃 ({timeout}초 초과)");
        }

        var output = stdout.ToString().TrimEnd();
        var error = stderr.ToString().TrimEnd();

        // 출력 크기 제한
        if (output.Length > 8000)
            output = output[..8000] + "\n... (출력 잘림)";

        var result = new StringBuilder();
        result.AppendLine($"[Exit code: {process.ExitCode}]");
        if (!string.IsNullOrEmpty(output))
            result.AppendLine(output);
        if (!string.IsNullOrEmpty(error))
            result.AppendLine($"[stderr]\n{error}");

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

    private static async Task<bool> IsToolAvailableAsync(string tool)
    {
        try
        {
            var psi = new ProcessStartInfo
            {
                FileName = Environment.OSVersion.Platform == PlatformID.Win32NT ? "where" : "which",
                Arguments = tool,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                CreateNoWindow = true,
            };
            using var process = Process.Start(psi);
            if (process == null) return false;
            await process.WaitForExitAsync();
            return process.ExitCode == 0;
        }
        catch (Exception)
        {
            return false;
        }
    }
}
