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

namespace AxCopilot.Services.Agent;

/// <summary>
/// AI 코드 리뷰 도구.
/// Git diff 분석, 파일 정적 검사, PR 요약 생성을 지원합니다.
/// LLM이 구조화된 분석 결과를 바탕으로 상세한 리뷰를 작성합니다.
/// </summary>
public class CodeReviewTool : IAgentTool
{
    public string Name => "code_review";

    public string Description =>
        "코드 리뷰를 수행합니다. Git diff 분석, 파일 정적 검사, PR 요약을 생성합니다.\n" +
        "action별 기능:\n" +
        "- diff_review: git diff 출력을 분석하여 이슈/개선점을 구조화\n" +
        "- file_review: 특정 파일의 코드 품질을 정적 검사\n" +
        "- pr_summary: 변경사항을 PR 설명 형식으로 요약";

    public ToolParameterSchema Parameters => new()
    {
        Properties = new()
        {
            ["action"] = new ToolProperty
            {
                Type = "string",
                Description = "리뷰 유형: diff_review (diff 분석), file_review (파일 검사), pr_summary (PR 요약)",
                Enum = ["diff_review", "file_review", "pr_summary"]
            },
            ["target"] = new ToolProperty
            {
                Type = "string",
                Description = "대상 지정. diff_review: '--staged' 또는 빈값(working tree). file_review: 파일 경로. pr_summary: 브랜치명(선택)."
            },
            ["focus"] = new ToolProperty
            {
                Type = "string",
                Description = "리뷰 초점: all (전체), bugs (버그), performance (성능), security (보안), style (스타일). 기본 all.",
                Enum = ["all", "bugs", "performance", "security", "style"]
            },
        },
        Required = ["action"]
    };

    public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
    {
        if (!(context.Llm?.Code?.EnableCodeReview ?? true))
            return ToolResult.Ok("코드 리뷰가 비활성 상태입니다. 설정 → AX Agent → 코드에서 활성화하세요.");

        var action = args.TryGetProperty("action", out var a) ? a.GetString() ?? "" : "";
        var target = args.TryGetProperty("target", out var t) ? t.GetString() ?? "" : "";
        var focus = args.TryGetProperty("focus", out var f) ? f.GetString() ?? "all" : "all";

        if (string.IsNullOrEmpty(context.WorkFolder))
            return ToolResult.Fail("작업 폴더가 설정되어 있지 않습니다.");

        return action switch
        {
            "diff_review" => await DiffReviewAsync(context, target, focus, ct),
            "file_review" => await FileReviewAsync(context, target, focus, ct),
            "pr_summary" => await PrSummaryAsync(context, target, ct),
            _ => ToolResult.Fail($"지원하지 않는 action: {action}. diff_review, file_review, pr_summary 중 선택하세요.")
        };
    }

    // ─── diff_review ─────────────────────────────────────────────────────────

    private async Task<ToolResult> DiffReviewAsync(AgentContext ctx, string target, string focus, CancellationToken ct)
    {
        var diffArgs = string.IsNullOrEmpty(target) ? "diff" : $"diff {target}";
        var diffResult = await RunGitAsync(ctx.WorkFolder, diffArgs, ct);
        if (diffResult == null) return ToolResult.Fail("Git을 찾을 수 없습니다.");
        if (string.IsNullOrWhiteSpace(diffResult))
            return ToolResult.Ok("변경사항이 없습니다. (clean working tree)");

        // diff 파싱
        var files = ParseDiffFiles(diffResult);
        var sb = new StringBuilder();
        sb.AppendLine("═══ Code Review Report (diff_review) ═══\n");

        // 통계
        int totalAdded = 0, totalRemoved = 0;
        foreach (var file in files)
        {
            totalAdded += file.Added;
            totalRemoved += file.Removed;
        }
        sb.AppendLine($"📊 파일 {files.Count}개 변경  |  +{totalAdded} 추가  -{totalRemoved} 삭제\n");
        sb.AppendLine($"🔍 리뷰 초점: {focus}\n");

        foreach (var file in files)
        {
            sb.AppendLine($"─── {file.Path} ({file.Status}) ───");
            sb.AppendLine($"  변경: +{file.Added} -{file.Removed}");

            // 정적 패턴 검사
            var issues = AnalyzeDiffHunks(file.Hunks, focus);
            if (issues.Count > 0)
            {
                foreach (var issue in issues)
                    sb.AppendLine($"  [{issue.Severity}] Line {issue.Line}: {issue.Message}");
            }
            else
            {
                sb.AppendLine("  [OK] 정적 검사에서 특이사항 없음");
            }
            sb.AppendLine();
        }

        sb.AppendLine("═══ 위 분석 결과를 바탕으로 상세한 코드 리뷰를 작성하세요. ═══");
        return ToolResult.Ok(sb.ToString());
    }

    // ─── file_review ─────────────────────────────────────────────────────────

    private async Task<ToolResult> FileReviewAsync(AgentContext ctx, string target, string focus, CancellationToken ct)
    {
        if (string.IsNullOrEmpty(target))
            return ToolResult.Fail("file_review에는 target(파일 경로)이 필요합니다.");

        var fullPath = Path.IsPathRooted(target) ? target : Path.Combine(ctx.WorkFolder, target);
        if (!File.Exists(fullPath))
            return ToolResult.Fail($"파일을 찾을 수 없습니다: {target}");
        if (!ctx.IsPathAllowed(fullPath))
            return ToolResult.Fail($"접근이 차단된 경로입니다: {target}");

        var content = await File.ReadAllTextAsync(fullPath, ct);
        var lines = content.Split('\n');

        var sb = new StringBuilder();
        sb.AppendLine($"═══ Code Review Report (file_review) ═══\n");
        sb.AppendLine($"📁 파일: {target}");
        sb.AppendLine($"📏 {lines.Length}줄  |  🔍 초점: {focus}\n");

        var issues = AnalyzeFile(lines, focus);

        if (issues.Count > 0)
        {
            sb.AppendLine($"⚠️ 발견된 이슈 {issues.Count}개:\n");
            foreach (var issue in issues)
                sb.AppendLine($"  [{issue.Severity}] Line {issue.Line}: {issue.Message}");
        }
        else
        {
            sb.AppendLine("✅ 정적 검사에서 특이사항 없음");
        }

        // 파일 앞부분 제공 (LLM이 상세 리뷰할 수 있도록)
        sb.AppendLine($"\n─── 파일 내용 (처음 200줄) ───");
        var preview = string.Join('\n', lines.Take(200));
        sb.AppendLine(preview);
        if (lines.Length > 200)
            sb.AppendLine($"\n... ({lines.Length - 200}줄 생략)");

        sb.AppendLine("\n═══ 위 분석 결과와 코드를 바탕으로 상세한 리뷰를 작성하세요. ═══");
        return ToolResult.Ok(sb.ToString());
    }

    // ─── pr_summary ──────────────────────────────────────────────────────────

    private async Task<ToolResult> PrSummaryAsync(AgentContext ctx, string target, CancellationToken ct)
    {
        var sb = new StringBuilder();
        sb.AppendLine("═══ PR Summary Data ═══\n");

        // git log
        var logArgs = string.IsNullOrEmpty(target)
            ? "log --oneline -20"
            : $"log --oneline {target}..HEAD";
        var log = await RunGitAsync(ctx.WorkFolder, logArgs, ct);
        if (!string.IsNullOrWhiteSpace(log))
        {
            sb.AppendLine("📋 커밋 이력:");
            sb.AppendLine(log);
            sb.AppendLine();
        }

        // git diff --stat
        var statArgs = string.IsNullOrEmpty(target) ? "diff --stat" : $"diff --stat {target}..HEAD";
        var stat = await RunGitAsync(ctx.WorkFolder, statArgs, ct);
        if (!string.IsNullOrWhiteSpace(stat))
        {
            sb.AppendLine("📊 변경 통계:");
            sb.AppendLine(stat);
            sb.AppendLine();
        }

        // git status
        var status = await RunGitAsync(ctx.WorkFolder, "status --short", ct);
        if (!string.IsNullOrWhiteSpace(status))
        {
            sb.AppendLine("📁 현재 상태:");
            sb.AppendLine(status);
            sb.AppendLine();
        }

        sb.AppendLine("═══ 위 데이터를 바탕으로 PR 제목과 설명(Summary, Changes, Test Plan)을 작성하세요. ═══");
        return ToolResult.Ok(sb.ToString());
    }

    // ─── 정적 분석 ──────────────────────────────────────────────────────────

    private static List<ReviewIssue> AnalyzeDiffHunks(List<string> hunks, string focus)
    {
        var issues = new List<ReviewIssue>();
        int lineNum = 0;

        foreach (var line in hunks)
        {
            // @@ -a,b +c,d @@ 에서 라인 번호 추출
            var hunkMatch = Regex.Match(line, @"@@ -\d+(?:,\d+)? \+(\d+)");
            if (hunkMatch.Success)
            {
                lineNum = int.Parse(hunkMatch.Groups[1].Value);
                continue;
            }

            if (!line.StartsWith('+') || line.StartsWith("+++")) continue;
            lineNum++;

            var content = line[1..];

            if (focus is "all" or "bugs")
            {
                if (Regex.IsMatch(content, @"catch\s*\{?\s*\}"))
                    issues.Add(new(lineNum, "WARNING", "빈 catch 블록 — 예외가 무시됩니다"));
                if (Regex.IsMatch(content, @"\.Result\b|\.Wait\(\)"))
                    issues.Add(new(lineNum, "WARNING", "동기 대기 (.Result/.Wait()) — 데드락 위험"));
                if (Regex.IsMatch(content, @"==\s*null") && content.Contains('.'))
                    issues.Add(new(lineNum, "INFO", "null 비교 — null 조건 연산자(?.) 사용 검토"));
            }

            if (focus is "all" or "security")
            {
                if (Regex.IsMatch(content, @"(password|secret|token|api_?key)\s*=\s*""[^""]+""", RegexOptions.IgnoreCase))
                    issues.Add(new(lineNum, "CRITICAL", "하드코딩된 비밀번호/키 감지"));
                if (Regex.IsMatch(content, @"(TODO|FIXME|HACK|XXX)\b", RegexOptions.IgnoreCase))
                    issues.Add(new(lineNum, "INFO", $"TODO/FIXME 마커 발견: {content.Trim()}"));
            }

            if (focus is "all" or "performance")
            {
                if (Regex.IsMatch(content, @"new\s+List<.*>\(\).*\.Add\(") || Regex.IsMatch(content, @"\.ToList\(\).*\.Where\("))
                    issues.Add(new(lineNum, "INFO", "불필요한 컬렉션 할당 가능성"));
                if (Regex.IsMatch(content, @"string\s*\+\s*=|"".*""\s*\+\s*"""))
                    issues.Add(new(lineNum, "INFO", "문자열 연결 — StringBuilder 사용 검토"));
            }

            if (focus is "all" or "style")
            {
                if (content.Length > 150)
                    issues.Add(new(lineNum, "STYLE", $"긴 라인 ({content.Length}자) — 가독성 저하"));
            }
        }

        return issues;
    }

    private static List<ReviewIssue> AnalyzeFile(string[] lines, string focus)
    {
        var issues = new List<ReviewIssue>();

        // 메서드 길이 추정 (중괄호 카운팅)
        int braceDepth = 0, methodStart = 0;
        bool inMethod = false;

        for (int i = 0; i < lines.Length; i++)
        {
            var line = lines[i];
            var lineNum = i + 1;
            var trimmed = line.TrimStart();

            // 메서드 시작 감지 (간단 휴리스틱)
            if (Regex.IsMatch(trimmed, @"(public|private|protected|internal|static|async|override)\s+.*\(.*\)\s*\{?\s*$") && !trimmed.Contains(';'))
            {
                inMethod = true;
                methodStart = lineNum;
            }

            if (trimmed.Contains('{')) braceDepth++;
            if (trimmed.Contains('}'))
            {
                braceDepth--;
                if (inMethod && braceDepth <= 1)
                {
                    var methodLen = lineNum - methodStart;
                    if (methodLen > 60 && (focus is "all" or "style"))
                        issues.Add(new(methodStart, "STYLE", $"긴 메서드 ({methodLen}줄) — 분할 검토"));
                    inMethod = false;
                }
            }

            if (focus is "all" or "bugs")
            {
                if (Regex.IsMatch(trimmed, @"catch\s*(\(\s*Exception)?\s*\)?\s*\{\s*\}"))
                    issues.Add(new(lineNum, "WARNING", "빈 catch 블록"));
                if (Regex.IsMatch(trimmed, @"\.Result\b|\.Wait\(\)"))
                    issues.Add(new(lineNum, "WARNING", "동기 대기 (.Result/.Wait()) — 데드락 위험"));
            }

            if (focus is "all" or "security")
            {
                if (Regex.IsMatch(trimmed, @"(password|secret|token|api_?key)\s*=\s*""[^""]+""", RegexOptions.IgnoreCase))
                    issues.Add(new(lineNum, "CRITICAL", "하드코딩된 비밀번호/키"));
                if (Regex.IsMatch(trimmed, @"(TODO|FIXME|HACK|XXX)\b", RegexOptions.IgnoreCase))
                    issues.Add(new(lineNum, "INFO", $"마커: {trimmed.Trim()}"));
            }

            if (focus is "all" or "performance")
            {
                if (Regex.IsMatch(trimmed, @"string\s*\+\s*="))
                    issues.Add(new(lineNum, "INFO", "루프 내 문자열 연결 — StringBuilder 검토"));
            }

            if (focus is "all" or "style")
            {
                if (line.Length > 150)
                    issues.Add(new(lineNum, "STYLE", $"긴 라인 ({line.Length}자)"));
            }
        }

        // 파일 전체 크기
        if (lines.Length > 500 && (focus is "all" or "style"))
            issues.Add(new(1, "STYLE", $"큰 파일 ({lines.Length}줄) — 클래스 분할 검토"));

        return issues;
    }

    // ─── Git 실행 헬퍼 ──────────────────────────────────────────────────────

    private static async Task<string?> RunGitAsync(string workDir, string args, CancellationToken ct)
    {
        var gitPath = FindGit();
        if (gitPath == null) return null;

        try
        {
            var psi = new ProcessStartInfo(gitPath, args)
            {
                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 null;

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

            return stdout.Length > 12000 ? stdout[..12000] + "\n... (출력 잘림)" : stdout;
        }
        catch (Exception) { return null; }
    }

    private static string? FindGit()
    {
        var paths = new[] { "git", @"C:\Program Files\Git\bin\git.exe", @"C:\Program Files (x86)\Git\bin\git.exe" };
        foreach (var p in paths)
        {
            try
            {
                var psi = new ProcessStartInfo(p, "--version")
                { RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true };
                using var proc = Process.Start(psi);
                proc?.WaitForExit(3000);
                if (proc?.ExitCode == 0) return p;
            }
            catch (Exception) { }
        }
        return null;
    }

    // ─── Diff 파서 ──────────────────────────────────────────────────────────

    private static List<DiffFile> ParseDiffFiles(string diff)
    {
        var files = new List<DiffFile>();
        DiffFile? current = null;

        foreach (var line in diff.Split('\n'))
        {
            if (line.StartsWith("diff --git"))
            {
                current = new DiffFile();
                files.Add(current);
                // "diff --git a/path b/path" → path 추출
                var parts = line.Split(" b/");
                current.Path = parts.Length > 1 ? parts[1].Trim() : line;
            }
            else if (current != null)
            {
                if (line.StartsWith("new file")) current.Status = "added";
                else if (line.StartsWith("deleted file")) current.Status = "deleted";
                else if (line.StartsWith("@@") || line.StartsWith("+") || line.StartsWith("-"))
                {
                    current.Hunks.Add(line);
                    if (line.StartsWith("+") && !line.StartsWith("+++")) current.Added++;
                    if (line.StartsWith("-") && !line.StartsWith("---")) current.Removed++;
                }
            }
        }
        return files;
    }

    private class DiffFile
    {
        public string Path { get; set; } = "";
        public string Status { get; set; } = "modified";
        public int Added { get; set; }
        public int Removed { get; set; }
        public List<string> Hunks { get; } = new();
    }

    private record ReviewIssue(int Line, string Severity, string Message);
}
