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

namespace AxCopilot.Services.Agent;

/// <summary>
/// Git 버전 관리 도구.
/// 사내 GitHub Enterprise 환경을 고려하여 안전한 Git 작업을 지원합니다.
/// push/force push는 차단되며, 사용자가 직접 수행해야 합니다.
/// </summary>
public class GitTool : IAgentTool, IConditionalTool
{
    /// <summary>작업 폴더에 .git이 있을 때만 활성화.</summary>
    public bool IsEnabled(ToolEnvironmentContext env) => env.HasGitRepo;

    public string Name => "git_tool";
    public string Description =>
        "Execute safe Git operations. Supports: status, diff, log, add, commit, branch, checkout. " +
        "Push operations are blocked for safety — user must push manually. " +
        "Works with enterprise GitHub (on-premise) repositories.";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["action"] = new()
            {
                Type = "string",
                Description = "Git action: status, diff, log, add, commit, branch, checkout, stash, remote",
                Enum = ["status", "diff", "log", "add", "commit", "branch", "checkout", "stash", "remote"],
            },
            ["args"] = new()
            {
                Type = "string",
                Description = "Additional arguments. For commit: commit message. For add: file path(s). For log: '--oneline -10'. For diff: file path or '--staged'.",
            },
        },
        Required = ["action"]
    };

    // 차단 명령 패턴 — 원격 수정 및 위험 작업
    private static readonly string[] BlockedPatterns =
    [
        "push", "push --force", "push -f",
        "pull", "fetch",
        "reset --hard", "clean -f",
        "rebase", "merge",
        "remote add", "remote remove", "remote set-url",
        "branch -D", "branch -d",
        "tag -d", "tag -D",
    ];

    public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct)
    {
        if (!args.TryGetProperty("action", out var actionEl))
            return ToolResult.Fail("action이 필요합니다.");
        var action = actionEl.GetString() ?? "status";
        var extraArgs = args.TryGetProperty("args", out var a) ? a.GetString() ?? "" : "";

        var workDir = context.WorkFolder;
        if (string.IsNullOrEmpty(workDir) || !Directory.Exists(workDir))
            return ToolResult.Fail("작업 폴더가 설정되지 않았습니다.");

        // Git 설치 확인
        var gitPath = FindGit();
        if (gitPath == null)
            return ToolResult.Fail("Git이 설치되어 있지 않습니다. PATH에 git이 있는지 확인하세요.");

        // Git 저장소 확인
        if (!Directory.Exists(Path.Combine(workDir, ".git")))
        {
            // 상위 디렉토리에서 .git 확인 (서브디렉토리 작업 지원)
            var checkDir = workDir;
            bool found = false;
            while (!string.IsNullOrEmpty(checkDir))
            {
                if (Directory.Exists(Path.Combine(checkDir, ".git"))) { found = true; break; }
                var parent = Directory.GetParent(checkDir)?.FullName;
                if (parent == checkDir) break;
                checkDir = parent;
            }
            if (!found)
                return ToolResult.Fail("현재 작업 폴더는 Git 저장소가 아닙니다.");
        }

        // 명령 구성
        var gitCommand = action switch
        {
            "status" => "status --short --branch",
            "diff" => string.IsNullOrEmpty(extraArgs) ? "diff" : $"diff {extraArgs}",
            "log" => string.IsNullOrEmpty(extraArgs) ? "log --oneline -15" : $"log {extraArgs}",
            "add" => string.IsNullOrEmpty(extraArgs) ? "add -A" : $"add {extraArgs}",
            "commit" => string.IsNullOrEmpty(extraArgs)
                ? null // 커밋 메시지 필수
                : $"commit -m \"{extraArgs.Replace("\"", "\\\"")}\"",
            "branch" => string.IsNullOrEmpty(extraArgs) ? "branch -a" : $"branch {extraArgs}",
            "checkout" => string.IsNullOrEmpty(extraArgs) ? null : $"checkout {extraArgs}",
            "stash" => string.IsNullOrEmpty(extraArgs) ? "stash list" : $"stash {extraArgs}",
            "remote" => "remote -v",
            _ => null,
        };

        if (gitCommand == null)
        {
            if (action == "commit")
                return ToolResult.Fail("커밋 메시지가 필요합니다. args에 커밋 메시지를 지정하세요.");
            if (action == "checkout")
                return ToolResult.Fail("체크아웃할 브랜치/파일을 args에 지정하세요.");
            return ToolResult.Fail($"알 수 없는 액션: {action}");
        }

        // 위험 명령 차단
        var fullCmd = $"git {gitCommand}";
        foreach (var pattern in BlockedPatterns)
        {
            if (fullCmd.Contains(pattern, StringComparison.OrdinalIgnoreCase))
                return ToolResult.Fail(
                    $"안전을 위해 '{pattern}' 작업은 차단됩니다.\n" +
                    "원격 저장소 작업(push/pull/fetch)과 이력 변경 작업은 사용자가 직접 수행하세요.");
        }

        // 쓰기 작업은 권한 확인
        var writeActions = new HashSet<string> { "add", "commit", "checkout", "stash" };
        if (writeActions.Contains(action))
        {
            if (!await context.CheckWritePermissionAsync(Name, workDir))
                return ToolResult.Fail("Git 쓰기 권한이 거부되었습니다.");
        }

        // Git 커밋 — 현재 비활성 (향후 활성화 예정)
        // 의사결정 수준에서 무조건 확인을 받더라도, 커밋 자체를 차단합니다.
        if (action == "commit")
        {
            return ToolResult.Fail(
                "Git 커밋 기능은 현재 비활성 상태입니다.\n" +
                "안전을 위해 커밋은 사용자가 직접 수행하세요.\n" +
                "향후 버전에서 활성화될 예정입니다.");
        }

        // 명령 실행
        try
        {
            var psi = new ProcessStartInfo(gitPath, gitCommand)
            {
                WorkingDirectory = workDir,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
                StandardOutputEncoding = Encoding.UTF8,
                StandardErrorEncoding = Encoding.UTF8,
            };

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

            using var proc = Process.Start(psi);
            if (proc == null) return ToolResult.Fail("Git 프로세스 시작 실패");

            var stdout = await proc.StandardOutput.ReadToEndAsync(cts.Token);
            var stderr = await proc.StandardError.ReadToEndAsync(cts.Token);
            await proc.WaitForExitAsync(cts.Token);

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

            var sb = new StringBuilder();
            sb.AppendLine($"[git {action}] Exit code: {proc.ExitCode}");
            if (!string.IsNullOrWhiteSpace(stdout)) sb.Append(stdout);
            if (!string.IsNullOrWhiteSpace(stderr) && proc.ExitCode != 0) sb.AppendLine($"\n[stderr] {stderr.Trim()}");

            return proc.ExitCode == 0
                ? ToolResult.Ok(sb.ToString())
                : ToolResult.Fail(sb.ToString());
        }
        catch (OperationCanceledException)
        {
            return ToolResult.Fail("Git 명령 타임아웃 (30초)");
        }
        catch (Exception ex)
        {
            return ToolResult.Fail($"Git 실행 오류: {ex.Message}");
        }
    }

    private static string? FindGit()
    {
        try
        {
            var psi = new ProcessStartInfo("where.exe", "git")
            {
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            };
            using var proc = Process.Start(psi);
            if (proc == null) return null;
            var output = proc.StandardOutput.ReadToEnd().Trim();
            proc.WaitForExit(5000);
            return string.IsNullOrWhiteSpace(output) ? null : output.Split('\n')[0].Trim();
        }
        catch (Exception) { return null; }
    }
}
