using System.Text.Json;
using System.Text.RegularExpressions;
using AxCopilot.Models;

namespace AxCopilot.Services.Agent;

/// <summary>
/// Phase 18-A1: 코디네이터 에이전트 서비스.
/// 사용자 요청을 분석하여 멀티 서브태스크 계획을 수립하고,
/// 각 태스크를 특화 서브에이전트에 위임하여 실행합니다.
/// </summary>
public class CoordinatorAgentService
{
    private readonly LlmService _llm;

    // 사용 가능한 에이전트 유형
    private static readonly string[] KnownAgentTypes =
    {
        "implementer",   // 코드 작성·수정
        "researcher",    // 조사·분석·요약
        "reviewer",      // 코드·문서 리뷰
        "documenter",    // 문서·보고서 작성
        "tester",        // 테스트 생성·실행
    };

    private const string PlanSystemPrompt = @"당신은 복잡한 작업을 분석하여 서브태스크로 분해하는 코디네이터 에이전트입니다.
사용자의 요청을 받아 실행 계획을 JSON 형식으로 반환하세요.

사용 가능한 에이전트 유형:
- implementer: 코드 작성 및 수정
- researcher: 조사, 분석, 요약
- reviewer: 코드·문서 리뷰
- documenter: 문서·보고서 작성
- tester: 테스트 생성 및 실행

반환 형식 (JSON만 반환, 설명 없음):
{
  ""original_request"": ""원본 요청"",
  ""tasks"": [
    {
      ""id"": ""task1"",
      ""agent_type"": ""researcher"",
      ""description"": ""태스크 설명"",
      ""dependencies"": []
    },
    {
      ""id"": ""task2"",
      ""agent_type"": ""implementer"",
      ""description"": ""태스크 설명"",
      ""dependencies"": [""task1""]
    }
  ]
}

규칙:
- 독립적으로 실행 가능한 태스크는 의존성 없이 병렬 실행 가능
- 선행 태스크 결과가 필요한 경우에만 dependencies에 추가
- 최대 6개 태스크로 분해
- JSON 외 다른 텍스트 없이 순수 JSON만 반환";

    public CoordinatorAgentService(LlmService llm)
    {
        _llm = llm;
    }

    /// <summary>사용자 요청을 분석하여 CoordinatorPlan을 생성합니다.</summary>
    public async Task<CoordinatorPlan> PlanAsync(string request, CancellationToken ct)
    {
        var messages = new List<ChatMessage>
        {
            new() { Role = "system", Content = PlanSystemPrompt },
            new() { Role = "user",   Content = $"다음 요청을 분석하여 실행 계획을 수립하세요:\n\n{request}" }
        };

        var response = await _llm.SendAsync(messages, ct);
        return ParsePlan(request, response);
    }

    /// <summary>
    /// CoordinatorPlan을 실행합니다. 의존성 순서에 따라 태스크를 실행하고
    /// 각 태스크 결과를 후속 태스크에 컨텍스트로 전달합니다.
    /// </summary>
    /// <param name="plan">실행할 계획</param>
    /// <param name="workFolder">작업 폴더</param>
    /// <param name="subAgentRunner">서브에이전트 실행기 (agentType, task, context, ct) → result</param>
    /// <param name="onTaskStarted">태스크 시작 콜백 (taskId, description)</param>
    /// <param name="onTaskCompleted">태스크 완료 콜백 (taskId, result)</param>
    /// <param name="ct">취소 토큰</param>
    public async Task<string> ExecuteAsync(
        CoordinatorPlan plan,
        string workFolder,
        Func<string, string, string, CancellationToken, Task<string>> subAgentRunner,
        Action<string, string>? onTaskStarted = null,
        Action<string, string>? onTaskCompleted = null,
        CancellationToken ct = default)
    {
        var results = new Dictionary<string, string>();  // taskId → result
        var remaining = plan.Tasks.ToList();

        // 의존성 기반 토폴로지 정렬 실행
        while (remaining.Count > 0 && !ct.IsCancellationRequested)
        {
            // 현재 실행 가능한 태스크 찾기 (의존성이 모두 완료된 것)
            var ready = remaining
                .Where(t => t.Dependencies.All(dep => results.ContainsKey(dep)))
                .ToList();

            if (ready.Count == 0)
            {
                // 순환 의존성 또는 누락된 의존성 — 강제 진행
                ready = new List<CoordinatorSubTask> { remaining[0] };
                LogService.Warn($"[Coordinator] 의존성 해결 실패, 강제 실행: {remaining[0].Id}");
            }

            // 의존성 없는 태스크는 병렬 실행
            var parallelTasks = ready.Select(async task =>
            {
                task.Status = SubTaskStatus.Running;
                onTaskStarted?.Invoke(task.Id, task.Description);

                // 선행 태스크 결과를 컨텍스트로 구성
                var context = BuildContext(task, results, plan.OriginalRequest);

                try
                {
                    var result = await subAgentRunner(task.AgentType, task.Description, context, ct);
                    task.Result = result;
                    task.Status = SubTaskStatus.Completed;
                    onTaskCompleted?.Invoke(task.Id, result);
                    return (task.Id, result, (Exception?)null);
                }
                catch (Exception ex)
                {
                    task.Error = ex.Message;
                    task.Status = SubTaskStatus.Failed;
                    onTaskCompleted?.Invoke(task.Id, $"[오류] {ex.Message}");
                    return (task.Id, $"[오류] {ex.Message}", ex);
                }
            });

            var parallelResults = await Task.WhenAll(parallelTasks);
            foreach (var (id, result, _) in parallelResults)
                results[id] = result;

            // 완료된 태스크 제거
            foreach (var t in ready)
                remaining.Remove(t);
        }

        // 최종 결과 합성
        return await SynthesizeResultAsync(plan, results, ct);
    }

    /// <summary>모든 서브태스크 결과를 종합하여 최종 응답을 생성합니다.</summary>
    private async Task<string> SynthesizeResultAsync(
        CoordinatorPlan plan,
        Dictionary<string, string> results,
        CancellationToken ct)
    {
        if (results.Count == 0) return "실행된 태스크가 없습니다.";

        // 단일 태스크면 바로 반환
        if (plan.Tasks.Count == 1 && results.Count == 1)
            return results.Values.First();

        var sb = new System.Text.StringBuilder();
        sb.AppendLine($"원본 요청: {plan.OriginalRequest}");
        sb.AppendLine();
        foreach (var task in plan.Tasks)
        {
            if (results.TryGetValue(task.Id, out var result))
            {
                sb.AppendLine($"[{task.AgentType}] {task.Description}:");
                sb.AppendLine(result.Length > 500 ? result[..500] + "…" : result);
                sb.AppendLine();
            }
        }

        var synthMessages = new List<ChatMessage>
        {
            new() { Role = "system", Content = "당신은 여러 서브에이전트 결과를 종합하는 코디네이터입니다. 결과를 간결하게 통합하여 최종 응답을 제공하세요." },
            new() { Role = "user",   Content = sb.ToString() }
        };

        return await _llm.SendAsync(synthMessages, ct);
    }

    // ─── 내부 헬퍼 ──────────────────────────────────────────────────────────

    private static string BuildContext(
        CoordinatorSubTask task,
        Dictionary<string, string> previousResults,
        string originalRequest)
    {
        var ctx = new System.Text.StringBuilder();
        ctx.AppendLine($"원본 요청: {originalRequest}");
        ctx.AppendLine($"현재 태스크: {task.Description}");

        if (task.Dependencies.Count > 0)
        {
            ctx.AppendLine("\n선행 태스크 결과:");
            foreach (var dep in task.Dependencies)
            {
                if (previousResults.TryGetValue(dep, out var depResult))
                    ctx.AppendLine($"- {dep}: {(depResult.Length > 300 ? depResult[..300] + "…" : depResult)}");
            }
        }

        return ctx.ToString();
    }

    private static CoordinatorPlan ParsePlan(string originalRequest, string llmResponse)
    {
        try
        {
            // JSON 블록 추출 (```json ... ``` 또는 { ... })
            var json = ExtractJson(llmResponse);
            if (string.IsNullOrEmpty(json))
                return FallbackPlan(originalRequest);

            var plan = JsonSerializer.Deserialize<CoordinatorPlan>(json,
                new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

            if (plan == null || plan.Tasks.Count == 0)
                return FallbackPlan(originalRequest);

            // 에이전트 유형 검증
            foreach (var task in plan.Tasks)
            {
                if (!KnownAgentTypes.Contains(task.AgentType, StringComparer.OrdinalIgnoreCase))
                    task.AgentType = "implementer";
            }

            return plan;
        }
        catch (Exception ex)
        {
            LogService.Warn($"[Coordinator] 계획 파싱 실패: {ex.Message}");
            return FallbackPlan(originalRequest);
        }
    }

    private static string ExtractJson(string text)
    {
        // ```json ... ``` 블록
        var m = Regex.Match(text, @"```(?:json)?\s*([\s\S]*?)\s*```");
        if (m.Success) return m.Groups[1].Value.Trim();

        // 첫 번째 { ... } 블록
        var start = text.IndexOf('{');
        var end   = text.LastIndexOf('}');
        if (start >= 0 && end > start) return text[start..(end + 1)];

        return "";
    }

    private static CoordinatorPlan FallbackPlan(string request) => new()
    {
        OriginalRequest = request,
        Tasks = new List<CoordinatorSubTask>
        {
            new() { Id = "task1", AgentType = "implementer", Description = request, Dependencies = new() }
        }
    };
}
