using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace AxCopilot.Services.Agent;

public class TestLoopTool : IAgentTool
{
	private class FailureDetail
	{
		public string TestName { get; set; } = "";

		public string FilePath { get; set; } = "";

		public int Line { get; set; }

		public string Message { get; set; } = "";
	}

	public string Name => "test_loop";

	public string Description => "코드 변경에 대한 테스트를 자동으로 생성하고 실행합니다.\n- action=\"generate\": 변경된 파일에 대한 테스트 코드 생성 제안\n- action=\"run\": 프로젝트의 테스트를 실행하고 결과 반환\n- action=\"analyze\": 테스트 결과를 분석하여 수정 방향 제시\n- action=\"auto_fix\": 테스트 실행 → 실패 파싱 → 구조화된 수정 지침 반환 (반복 수정용)\n테스트 프레임워크를 자동 감지합니다 (xUnit, NUnit, MSTest, pytest, Jest 등).";

	public ToolParameterSchema Parameters => new ToolParameterSchema
	{
		Properties = new Dictionary<string, ToolProperty>
		{
			["action"] = new ToolProperty
			{
				Type = "string",
				Description = "수행할 작업: generate | run | analyze | auto_fix",
				Enum = new List<string> { "generate", "run", "analyze", "auto_fix" }
			},
			["file_path"] = new ToolProperty
			{
				Type = "string",
				Description = "대상 소스 파일 경로 (generate 시 필요)"
			},
			["test_output"] = new ToolProperty
			{
				Type = "string",
				Description = "분석할 테스트 출력 (analyze 시 필요)"
			}
		},
		Required = new List<string> { "action" }
	};

	public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default(CancellationToken))
	{
		JsonElement a;
		string action = (args.TryGetProperty("action", out a) ? (a.GetString() ?? "") : "");
		if (1 == 0)
		{
		}
		ToolResult result = action switch
		{
			"generate" => GenerateTestSuggestion(args, context), 
			"run" => await RunTestsAsync(context, ct), 
			"analyze" => AnalyzeTestOutput(args), 
			"auto_fix" => await AutoFixAsync(context, ct), 
			_ => ToolResult.Fail("action은 generate, run, analyze, auto_fix 중 하나여야 합니다."), 
		};
		if (1 == 0)
		{
		}
		return result;
	}

	private static ToolResult GenerateTestSuggestion(JsonElement args, AgentContext context)
	{
		JsonElement value;
		string text = (args.TryGetProperty("file_path", out value) ? (value.GetString() ?? "") : "");
		if (string.IsNullOrEmpty(text))
		{
			return ToolResult.Fail("file_path가 필요합니다.");
		}
		if (!Path.IsPathRooted(text) && !string.IsNullOrEmpty(context.WorkFolder))
		{
			text = Path.Combine(context.WorkFolder, text);
		}
		if (!File.Exists(text))
		{
			return ToolResult.Fail("파일 없음: " + text);
		}
		string text2 = Path.GetExtension(text).ToLowerInvariant();
		if (1 == 0)
		{
		}
		(string, string, string) tuple;
		switch (text2)
		{
		case ".cs":
			tuple = ("xUnit/NUnit/MSTest", ".cs", "ClassNameTests.cs");
			break;
		case ".py":
			tuple = ("pytest", ".py", "test_module.py");
			break;
		case ".ts":
		case ".tsx":
			tuple = ("Jest/Vitest", ".test.ts", "Component.test.ts");
			break;
		case ".js":
		case ".jsx":
			tuple = ("Jest", ".test.js", "module.test.js");
			break;
		case ".java":
			tuple = ("JUnit", ".java", "ClassTest.java");
			break;
		case ".go":
			tuple = ("go test", "_test.go", "module_test.go");
			break;
		default:
			tuple = ("unknown", text2, "test" + text2);
			break;
		}
		if (1 == 0)
		{
		}
		(string, string, string) tuple2 = tuple;
		string item = tuple2.Item1;
		string item2 = tuple2.Item2;
		string item3 = tuple2.Item3;
		string text3 = File.ReadAllText(text);
		int value2 = text3.Split('\n').Length;
		return ToolResult.Ok($"테스트 생성 제안:\n  대상 파일: {Path.GetFileName(text)} ({value2}줄)\n  감지된 프레임워크: {item}\n  테스트 파일 명명: {item3}\n  테스트 파일 확장자: {item2}\n\nfile_write 도구로 테스트 파일을 생성한 후, test_loop action=\"run\"으로 실행하세요.");
	}

	private static async Task<ToolResult> RunTestsAsync(AgentContext context, CancellationToken ct)
	{
		if (string.IsNullOrEmpty(context.WorkFolder))
		{
			return ToolResult.Fail("작업 폴더가 설정되어 있지 않습니다.");
		}
		var (cmd, cmdArgs) = DetectTestCommand(context.WorkFolder);
		if (cmd == null)
		{
			return ToolResult.Fail("테스트 프레임워크를 감지할 수 없습니다. 지원: .NET (dotnet test), Python (pytest), Node.js (npm test)");
		}
		try
		{
			ProcessStartInfo psi = new ProcessStartInfo
			{
				FileName = cmd,
				Arguments = cmdArgs,
				WorkingDirectory = context.WorkFolder,
				UseShellExecute = false,
				RedirectStandardOutput = true,
				RedirectStandardError = true,
				CreateNoWindow = true
			};
			using Process proc = Process.Start(psi);
			if (proc == null)
			{
				return ToolResult.Fail("테스트 프로세스 시작 실패");
			}
			using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
			cts.CancelAfter(TimeSpan.FromSeconds(120.0));
			string stdout = await proc.StandardOutput.ReadToEndAsync(cts.Token);
			string stderr = await proc.StandardError.ReadToEndAsync(cts.Token);
			await proc.WaitForExitAsync(cts.Token);
			StringBuilder sb = new StringBuilder();
			StringBuilder stringBuilder = sb;
			StringBuilder stringBuilder2 = stringBuilder;
			StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(24, 1, stringBuilder);
			handler.AppendLiteral("테스트 실행 결과 (exit code: ");
			handler.AppendFormatted(proc.ExitCode);
			handler.AppendLiteral("):");
			stringBuilder2.AppendLine(ref handler);
			stringBuilder = sb;
			StringBuilder stringBuilder3 = stringBuilder;
			handler = new StringBuilder.AppendInterpolatedStringHandler(5, 2, stringBuilder);
			handler.AppendLiteral("명령: ");
			handler.AppendFormatted(cmd);
			handler.AppendLiteral(" ");
			handler.AppendFormatted(cmdArgs);
			stringBuilder3.AppendLine(ref handler);
			sb.AppendLine();
			if (!string.IsNullOrWhiteSpace(stdout))
			{
				sb.AppendLine(stdout);
			}
			if (!string.IsNullOrWhiteSpace(stderr))
			{
				stringBuilder = sb;
				StringBuilder stringBuilder4 = stringBuilder;
				handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder);
				handler.AppendLiteral("[STDERR]\n");
				handler.AppendFormatted(stderr);
				stringBuilder4.AppendLine(ref handler);
			}
			return (proc.ExitCode == 0) ? ToolResult.Ok(sb.ToString()) : ToolResult.Ok($"테스트 실패 (exit code {proc.ExitCode}):\n{sb}");
		}
		catch (Exception ex)
		{
			return ToolResult.Fail("테스트 실행 오류: " + ex.Message);
		}
	}

	private static ToolResult AnalyzeTestOutput(JsonElement args)
	{
		JsonElement value;
		string text = (args.TryGetProperty("test_output", out value) ? (value.GetString() ?? "") : "");
		if (string.IsNullOrEmpty(text))
		{
			return ToolResult.Fail("test_output이 필요합니다.");
		}
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.AppendLine("테스트 결과 분석:");
		string[] source = text.Split('\n');
		int num = source.Count((string l) => l.Contains("FAIL", StringComparison.OrdinalIgnoreCase) || l.Contains("FAILED"));
		int value2 = source.Count((string l) => l.Contains("PASS", StringComparison.OrdinalIgnoreCase) || l.Contains("PASSED"));
		List<string> list = source.Where((string l) => l.Contains("Error", StringComparison.OrdinalIgnoreCase) || l.Contains("Exception")).Take(10).ToList();
		StringBuilder stringBuilder2 = stringBuilder;
		StringBuilder stringBuilder3 = stringBuilder2;
		StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(14, 2, stringBuilder2);
		handler.AppendLiteral("  통과: ");
		handler.AppendFormatted(value2);
		handler.AppendLiteral("개, 실패: ");
		handler.AppendFormatted(num);
		handler.AppendLiteral("개");
		stringBuilder3.AppendLine(ref handler);
		if (list.Count > 0)
		{
			stringBuilder.AppendLine("\n주요 오류:");
			foreach (string item in list)
			{
				stringBuilder2 = stringBuilder;
				StringBuilder stringBuilder4 = stringBuilder2;
				handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2);
				handler.AppendLiteral("  ");
				handler.AppendFormatted(item.Trim());
				stringBuilder4.AppendLine(ref handler);
			}
		}
		if (num > 0)
		{
			stringBuilder.AppendLine("\n다음 단계: 실패한 테스트를 확인하고 관련 코드를 수정한 후 test_loop action=\"run\"으로 다시 실행하세요.");
		}
		else
		{
			stringBuilder.AppendLine("\n모든 테스트가 통과했습니다.");
		}
		return ToolResult.Ok(stringBuilder.ToString());
	}

	private static async Task<ToolResult> AutoFixAsync(AgentContext context, CancellationToken ct)
	{
		ToolResult runResult = await RunTestsAsync(context, ct);
		string output = runResult.Output;
		string[] lines = output.Split('\n');
		int failedCount = lines.Count((string l) => l.Contains("FAIL", StringComparison.OrdinalIgnoreCase) || l.Contains("FAILED", StringComparison.OrdinalIgnoreCase));
		if (failedCount == 0 && runResult.Success && output.Contains("exit code: 0"))
		{
			return ToolResult.Ok("[AUTO_FIX: ALL_PASSED]\n모든 테스트가 통과했습니다. 수정 루프를 종료하세요.");
		}
		StringBuilder sb = new StringBuilder();
		sb.AppendLine("[AUTO_FIX: FAILURES_DETECTED]");
		StringBuilder stringBuilder = sb;
		StringBuilder stringBuilder2 = stringBuilder;
		StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(10, 1, stringBuilder);
		handler.AppendLiteral("실패 테스트 수: ");
		handler.AppendFormatted(failedCount);
		stringBuilder2.AppendLine(ref handler);
		sb.AppendLine();
		List<FailureDetail> errors = ExtractFailureDetails(lines);
		if (errors.Count > 0)
		{
			sb.AppendLine("## 실패 상세:");
			foreach (FailureDetail err in errors.Take(10))
			{
				stringBuilder = sb;
				StringBuilder stringBuilder3 = stringBuilder;
				handler = new StringBuilder.AppendInterpolatedStringHandler(7, 1, stringBuilder);
				handler.AppendLiteral("- 테스트: ");
				handler.AppendFormatted(err.TestName);
				stringBuilder3.AppendLine(ref handler);
				if (!string.IsNullOrEmpty(err.FilePath))
				{
					stringBuilder = sb;
					StringBuilder stringBuilder4 = stringBuilder;
					handler = new StringBuilder.AppendInterpolatedStringHandler(7, 2, stringBuilder);
					handler.AppendLiteral("  파일: ");
					handler.AppendFormatted(err.FilePath);
					handler.AppendLiteral(":");
					handler.AppendFormatted(err.Line);
					stringBuilder4.AppendLine(ref handler);
				}
				stringBuilder = sb;
				StringBuilder stringBuilder5 = stringBuilder;
				handler = new StringBuilder.AppendInterpolatedStringHandler(6, 1, stringBuilder);
				handler.AppendLiteral("  오류: ");
				handler.AppendFormatted(err.Message);
				stringBuilder5.AppendLine(ref handler);
				sb.AppendLine();
			}
		}
		sb.AppendLine("## 수정 지침:");
		sb.AppendLine("1. 위 오류 메시지에서 원인을 파악하세요");
		sb.AppendLine("2. file_read로 관련 파일을 읽고 오류 원인을 확인하세요");
		sb.AppendLine("3. file_edit로 코드를 수정하세요");
		sb.AppendLine("4. test_loop action=\"auto_fix\"를 다시 호출하여 결과를 확인하세요");
		sb.AppendLine("5. 모든 테스트가 통과할 때까지 반복하세요");
		int maxIter = (Application.Current as App)?.SettingsService?.Settings.Llm.MaxTestFixIterations ?? 5;
		stringBuilder = sb;
		StringBuilder stringBuilder6 = stringBuilder;
		handler = new StringBuilder.AppendInterpolatedStringHandler(36, 1, stringBuilder);
		handler.AppendLiteral("\n※ 최대 수정 반복 횟수: ");
		handler.AppendFormatted(maxIter);
		handler.AppendLiteral("회. 초과 시 사용자에게 보고하세요.");
		stringBuilder6.AppendLine(ref handler);
		sb.AppendLine("\n## 전체 테스트 출력:");
		string truncated = ((output.Length > 3000) ? (output.Substring(0, 3000) + "\n... (출력 일부 생략)") : output);
		sb.AppendLine(truncated);
		return ToolResult.Ok(sb.ToString());
	}

	private static List<FailureDetail> ExtractFailureDetails(string[] lines)
	{
		List<FailureDetail> list = new List<FailureDetail>();
		FailureDetail failureDetail = null;
		for (int i = 0; i < lines.Length; i++)
		{
			string text = lines[i].Trim();
			if (text.StartsWith("Failed ", StringComparison.OrdinalIgnoreCase) || text.Contains("FAIL!", StringComparison.OrdinalIgnoreCase))
			{
				failureDetail = new FailureDetail
				{
					TestName = text
				};
				list.Add(failureDetail);
			}
			else if (text.StartsWith("FAILED ", StringComparison.OrdinalIgnoreCase))
			{
				FailureDetail failureDetail2 = new FailureDetail();
				string text2 = text;
				failureDetail2.TestName = text2.Substring(7, text2.Length - 7).Trim();
				failureDetail = failureDetail2;
				list.Add(failureDetail);
			}
			else if (failureDetail != null && string.IsNullOrEmpty(failureDetail.FilePath))
			{
				Match match = Regex.Match(text, "([^\\s]+\\.\\w+)[:\\(](\\d+)");
				if (match.Success)
				{
					failureDetail.FilePath = match.Groups[1].Value;
					failureDetail.Line = int.Parse(match.Groups[2].Value);
				}
			}
			else if (failureDetail != null && string.IsNullOrEmpty(failureDetail.Message) && (text.Contains("Assert", StringComparison.OrdinalIgnoreCase) || text.Contains("Error", StringComparison.OrdinalIgnoreCase) || text.Contains("Exception", StringComparison.OrdinalIgnoreCase)))
			{
				failureDetail.Message = text;
			}
		}
		return list;
	}

	private static (string? Cmd, string Args) DetectTestCommand(string workFolder)
	{
		if (Directory.EnumerateFiles(workFolder, "*.csproj", SearchOption.AllDirectories).Any() || Directory.EnumerateFiles(workFolder, "*.sln", SearchOption.TopDirectoryOnly).Any())
		{
			return (Cmd: "dotnet", Args: "test --no-build --verbosity normal");
		}
		if (File.Exists(Path.Combine(workFolder, "pytest.ini")) || File.Exists(Path.Combine(workFolder, "setup.py")) || Directory.EnumerateFiles(workFolder, "test_*.py", SearchOption.AllDirectories).Any())
		{
			return (Cmd: "pytest", Args: "--tb=short -q");
		}
		if (File.Exists(Path.Combine(workFolder, "package.json")))
		{
			return (Cmd: "npm", Args: "test -- --passWithNoTests");
		}
		if (Directory.EnumerateFiles(workFolder, "*_test.go", SearchOption.AllDirectories).Any())
		{
			return (Cmd: "go", Args: "test ./...");
		}
		return (Cmd: null, Args: "");
	}
}
