using System.Diagnostics;
using System.Text.Json;
using AxCopilot.Models;
using AxCopilot.Services;

namespace AxCopilot.Services.Agent;

public partial class AgentLoopService
{
    private static string TruncateOutput(string output, int maxLength)
    {
        if (output.Length <= maxLength) return output;
        return output[..maxLength] + "\n... (출력 잘림)";
    }

    // ─── 병렬 도구 실행 ──────────────────────────────────────────────────

    // 읽기 전용 도구 (파일 상태를 변경하지 않음)
    private static readonly HashSet<string> ReadOnlyTools = new(StringComparer.OrdinalIgnoreCase)
    {
        "file_read", "glob", "grep_tool", "folder_map", "document_read",
        "search_codebase", "code_search", "env_tool", "datetime_tool",
        "dev_env_detect", "memory", "skill_manager", "json_tool",
        "regex_tool", "base64_tool", "hash_tool", "image_analyze",
    };

    /// <summary>도구 호출을 병렬 가능 / 순차 필수로 분류합니다.</summary>
    private static (List<LlmService.ContentBlock> Parallel, List<LlmService.ContentBlock> Sequential)
        ClassifyToolCalls(List<LlmService.ContentBlock> calls)
    {
        var parallel = new List<LlmService.ContentBlock>();
        var sequential = new List<LlmService.ContentBlock>();

        foreach (var call in calls)
        {
            if (ReadOnlyTools.Contains(call.ToolName ?? ""))
                parallel.Add(call);
            else
                sequential.Add(call);
        }

        // 읽기 전용 도구가 1개뿐이면 병렬화 의미 없음
        if (parallel.Count <= 1)
        {
            sequential.InsertRange(0, parallel);
            parallel.Clear();
        }

        return (parallel, sequential);
    }

    // ─── Phase 33-B: 도구 실행 루프 상태 (RunAsync에서 추출) ─────────────
    /// <summary>도구 실행 루프의 가변 상태. 메서드 간 공유.</summary>
    private class ToolExecutionState
    {
        public int CurrentStep;
        public int TotalToolCalls;
        public int MaxIterations;
        public int BaseMax;
        public int MaxRetry;
        public int ConsecutiveErrors;
        public int StatsSuccessCount;
        public int StatsFailCount;
        public int StatsInputTokens;
        public int StatsOutputTokens;
        public List<string> StatsUsedTools = new();
        public List<string> PlanSteps = new();
        public bool DocumentPlanCalled;
        public string? DocumentPlanPath;
        public string? DocumentPlanTitle;
        public string? DocumentPlanScaffold;
    }

    /// <summary>병렬 실행용 가변 상태 (ToolExecutionState의 서브셋). TODO: ToolExecutionState로 통합 예정.</summary>
    private class ParallelState
    {
        public int CurrentStep;
        public int TotalToolCalls;
        public int MaxIterations;
        public int ConsecutiveErrors;
        public int StatsSuccessCount;
        public int StatsFailCount;
        public int StatsInputTokens;
        public int StatsOutputTokens;
    }

    /// <summary>
    /// Phase 33-B: 단일 도구 호출을 처리합니다 (RunAsync에서 추출).
    /// 훅 실행 → 도구 실행 → 결과 처리 → 검증까지 단일 도구 라이프사이클.
    /// </summary>
    /// <returns>LoopAction: Continue=다음 도구, Break=루프 중단, Return=값 반환.</returns>
    private async Task<(ToolCallAction Action, string? ReturnValue)> ProcessSingleToolCallAsync(
        LlmService.ContentBlock call,
        List<ChatMessage> messages,
        AgentContext context,
        ToolExecutionState state,
        List<LlmService.ContentBlock> toolCalls,
        string activeTabSnapshot,
        int iteration,
        CancellationToken ct)
    {
        var llm = _settings.Settings.Llm;
        var tool = _tools.Get(call.ToolName);
        if (tool == null)
        {
            var errResult = $"알 수 없는 도구: {call.ToolName}";
            EmitEvent(AgentEventType.Error, call.ToolName, errResult);
            messages.Add(LlmService.CreateToolResultMessage(call.ToolId, call.ToolName, errResult));
            return (ToolCallAction.Continue, null);
        }

        // Task Decomposition: 단계 진행률 추적
        if (state.PlanSteps.Count > 0)
        {
            var summary = FormatToolCallSummary(call);
            var newStep = TaskDecomposer.EstimateCurrentStep(
                state.PlanSteps, call.ToolName, summary, state.CurrentStep);
            if (newStep != state.CurrentStep)
            {
                state.CurrentStep = newStep;
                EmitEvent(AgentEventType.StepStart, "", state.PlanSteps[state.CurrentStep],
                    stepCurrent: state.CurrentStep + 1, stepTotal: state.PlanSteps.Count);
            }
        }

        // 개발자 모드: 도구 호출 파라미터 상세 표시
        if (context.DevMode)
        {
            var paramJson = call.ToolInput?.ToString() ?? "{}";
            if (paramJson.Length > 500) paramJson = paramJson[..500] + "...";
            EmitEvent(AgentEventType.Thinking, call.ToolName,
                $"[DEV] 도구 호출: {call.ToolName}\n파라미터: {paramJson}");
        }

        // 이벤트 로그 기록
        if (_eventLog != null)
            _ = _eventLog.AppendAsync(AgentEventLogType.ToolRequest,
                JsonSerializer.Serialize(new { toolName = call.ToolName, iteration }));

        // Phase 17-G2: 도구 위험도 태그 (Medium/High만 표시)
        var toolName17  = call.ToolName ?? "";
        var toolRisk    = ToolRiskMapper.GetRisk(toolName17);
        var riskTag     = toolRisk >= ToolRiskLevel.Medium ? $" [{ToolRiskMapper.GetRiskLabel(toolRisk)}]" : "";
        EmitEvent(AgentEventType.ToolCall, toolName17, FormatToolCallSummary(call) + riskTag);

        // 개발자 모드: 스텝 바이 스텝 승인
        if (context.DevModeStepApproval && UserDecisionCallback != null)
        {
            var decision = await UserDecisionCallback(
                $"[DEV] 도구 '{call.ToolName}' 실행을 승인하시겠습니까?\n{FormatToolCallSummary(call)}",
                new List<string> { "승인", "건너뛰기", "중단" });
            if (decision == "중단")
            {
                EmitEvent(AgentEventType.Complete, "", "[DEV] 사용자가 실행을 중단했습니다");
                return (ToolCallAction.Return, "사용자가 개발자 모드에서 실행을 중단했습니다.");
            }
            if (decision == "건너뛰기")
            {
                messages.Add(LlmService.CreateToolResultMessage(
                    call.ToolId ?? "", toolName17, "[SKIPPED by developer] 사용자가 이 도구 실행을 건너뛰었습니다."));
                return (ToolCallAction.Continue, null);
            }
        }

        // Phase 17-F: PermissionDecisionService 기반 권한 평가
        var toolInputStr = call.ToolInput?.ToString() ?? "";
        var permDecision = EvaluateToolPermission(call.ToolName ?? "", toolInputStr);

        if (permDecision == PermissionDecision.Deny)
        {
            var denyMsg = $"도구 '{toolName17}'이(가) 권한 정책에 의해 차단되었습니다. [{ToolRiskMapper.GetRiskLabel(toolRisk)}]";
            EmitEvent(AgentEventType.Error, toolName17, denyMsg);
            messages.Add(LlmService.CreateToolResultMessage(call.ToolId ?? "", toolName17, $"[DENIED] {denyMsg}"));
            return (ToolCallAction.Continue, null);
        }

        // Phase 17-F: Allow 결정(acceptEdits 등) 시 CheckDecisionRequired 스킵
        var skipLegacyDecisionCheck = permDecision == PermissionDecision.Allow;

        // 영향 범위 기반 의사결정 체크 (Phase 17-F Allow 시 건너뜀)
        var decisionRequired = skipLegacyDecisionCheck ? null : CheckDecisionRequired(call, context);
        if (decisionRequired != null && UserDecisionCallback != null)
        {
            var decision = await UserDecisionCallback(
                decisionRequired,
                new List<string> { "승인", "건너뛰기", "취소" });
            if (decision == "취소")
            {
                EmitEvent(AgentEventType.Complete, "", "사용자가 작업을 취소했습니다");
                return (ToolCallAction.Return, "사용자가 작업을 취소했습니다.");
            }
            if (decision == "건너뛰기")
            {
                messages.Add(LlmService.CreateToolResultMessage(
                    call.ToolId ?? "", toolName17, "[SKIPPED] 사용자가 이 작업을 건너뛰었습니다."));
                return (ToolCallAction.Continue, null);
            }
        }

        // ── Pre-Hook 실행 ──
        await RunToolHooksAsync(llm, call, context, "pre", ct);

        // Phase 17-G: Diff 추적 — 쓰기 도구 실행 전 원본 내용 캡처
        var diffFilePath       = ExtractFilePathFromInput(call.ToolInput);
        var diffOriginalContent = CaptureOriginalFileContent(diffFilePath);

        // ── 도구 실행 ──
        ToolResult result;
        var sw = Stopwatch.StartNew();
        try
        {
            var input = call.ToolInput ?? JsonDocument.Parse("{}").RootElement;
            result = await tool.ExecuteAsync(input, context, ct);
        }
        catch (OperationCanceledException)
        {
            EmitEvent(AgentEventType.Complete, "", "사용자가 작업을 취소했습니다.");
            return (ToolCallAction.Return, "사용자가 작업을 취소했습니다.");
        }
        catch (Exception ex)
        {
            result = ToolResult.Fail($"도구 실행 오류: {ex.Message}");
        }
        sw.Stop();

        // ── Post-Hook 실행 ──
        await RunToolHooksAsync(llm, call, context, "post", ct, result);

        // 개발자 모드: 도구 결과 상세 표시
        if (context.DevMode)
        {
            EmitEvent(AgentEventType.Thinking, toolName17,
                $"[DEV] 결과: {(result.Success ? "성공" : "실패")}\n{TruncateOutput(result.Output, 500)}");
        }

        var tokenUsage = _llm.LastTokenUsage;
        EmitEvent(
            result.Success ? AgentEventType.ToolResult : AgentEventType.Error,
            toolName17,
            TruncateOutput(result.Output, 200),
            result.FilePath,
            elapsedMs: sw.ElapsedMilliseconds,
            inputTokens: tokenUsage?.PromptTokens ?? 0,
            outputTokens: tokenUsage?.CompletionTokens ?? 0,
            toolInput: call.ToolInput?.ToString(),
            iteration: iteration);

        // 통계 수집
        if (result.Success) state.StatsSuccessCount++; else state.StatsFailCount++;
        state.StatsInputTokens += tokenUsage?.PromptTokens ?? 0;
        state.StatsOutputTokens += tokenUsage?.CompletionTokens ?? 0;
        if (!state.StatsUsedTools.Contains(toolName17))
            state.StatsUsedTools.Add(toolName17);

        // 감사 로그 기록
        if (llm.EnableAuditLog)
        {
            AuditLogService.LogToolCall(
                _conversationId, activeTabSnapshot,
                toolName17,
                call.ToolInput.ToString() ?? "",
                TruncateOutput(result.Output, 500),
                result.FilePath, result.Success);
        }

        // 이벤트 로그
        if (_eventLog != null)
            _ = _eventLog.AppendAsync(AgentEventLogType.ToolResult,
                JsonSerializer.Serialize(new
                {
                    toolName = call.ToolName,
                    success = result.Success,
                    outputLength = result.Output?.Length ?? 0
                }));

        state.TotalToolCalls++;

        // 동적 반복 한도 확장
        if (state.TotalToolCalls > Defaults.ToolCallThresholdForExpansion && state.MaxIterations < state.BaseMax * 2)
            state.MaxIterations = Math.Min(state.BaseMax * 2, 50);

        // 테스트-수정 루프 예산 확장
        if (call.ToolName == "test_loop" && (result.Output?.Contains("[AUTO_FIX:") ?? false))
        {
            var testFixMax = llm.MaxTestFixIterations > 0 ? llm.MaxTestFixIterations : Defaults.MaxTestFixIterations;
            var testFixBudget = state.BaseMax + testFixMax * 3;
            if (state.MaxIterations < testFixBudget)
                state.MaxIterations = Math.Min(testFixBudget, 60);
        }

        // UI 스레드가 이벤트를 렌더링할 시간 확보
        await Task.Delay(80, ct);

        // Self-Reflection: 도구 실패 시 반성 프롬프트 추가
        if (!result.Success)
        {
            state.ConsecutiveErrors++;
            if (state.ConsecutiveErrors <= state.MaxRetry)
            {
                var reflectionMsg = $"[Tool '{toolName17}' failed: {TruncateOutput(result.Output ?? "", 500)}]\n" +
                    $"Analyze why this failed. Consider: wrong parameters, wrong file path, missing prerequisites. " +
                    $"Try a different approach. (Error {state.ConsecutiveErrors}/{state.MaxRetry})";
                messages.Add(LlmService.CreateToolResultMessage(call.ToolId ?? "", toolName17, reflectionMsg));
                EmitEvent(AgentEventType.Thinking, "", $"Self-Reflection: 실패 분석 후 재시도 ({state.ConsecutiveErrors}/{state.MaxRetry})");
            }
            else
            {
                messages.Add(LlmService.CreateToolResultMessage(
                    call.ToolId ?? "", toolName17,
                    $"[FAILED after {state.MaxRetry} retries] {TruncateOutput(result.Output ?? "", 500)}\n" +
                    "Stop retrying this tool. Explain the error to the user and suggest alternative approaches."));

                // 에이전트 메모리에 실패 원인 저장
                try
                {
                    var memSvc = CurrentApp?.MemoryService;
                    if (memSvc != null && (_settings.Settings.Llm.EnableAgentMemory))
                    {
                        memSvc.Add("correction",
                            $"도구 '{call.ToolName}' 반복 실패: {TruncateOutput(result.Output ?? "", Defaults.LogSummaryMaxLength)}",
                            $"conv:{_conversationId}", context.WorkFolder);
                    }
                }
                catch (Exception ex) { LogService.Warn($"[AgentLoop] 에이전트 메모리 저장 실패: {ex.Message}"); }
            }
        }
        else
        {
            state.ConsecutiveErrors = 0;

            // Phase 17-B: 파일 경로를 Working Memory 참조 파일 목록에 추가
            TrackToolFile(result.FilePath);

            // Phase 17-D: paths: glob 패턴 매칭 스킬 자동 주입
            InjectPathBasedSkills(result.FilePath, messages);

            // Phase 17-E: .ax/rules/*.md 경로 범위 규칙 주입
            _ = InjectPathScopedRulesAsync(result.FilePath, messages, CancellationToken.None);

            // Phase 17-G: 멀티파일 Diff 변경 기록
            TrackFileModificationForDiff(call.ToolName ?? "", result.FilePath ?? diffFilePath, diffOriginalContent);

            // ToolResultSizer 적용
            var sizedResult = ToolResultSizer.Apply(result.Output ?? "", toolName17);
            messages.Add(LlmService.CreateToolResultMessage(call.ToolId ?? "", toolName17, sizedResult.Output));

            // document_plan 호출 플래그 + 메타데이터 저장
            if (call.ToolName == "document_plan" && result.Success)
            {
                state.DocumentPlanCalled = true;
                try
                {
                    var planJson = JsonDocument.Parse(result.Output ?? "{}");
                    if (planJson.RootElement.TryGetProperty("path", out var p))
                        state.DocumentPlanPath = p.GetString();
                    if (planJson.RootElement.TryGetProperty("title", out var t))
                        state.DocumentPlanTitle = t.GetString();
                    if (planJson.RootElement.TryGetProperty("body", out var b))
                        state.DocumentPlanScaffold = b.GetString();
                }
                catch (Exception) { /* document_plan JSON 파싱 실패 — 선택적 메타데이터이므로 무시 */ }

                // html_create 즉시 호출 유도
                var toolHint = (result.Output?.Contains("document_assemble") ?? false) ? "document_assemble" :
                               (result.Output?.Contains("file_write") ?? false) ? "file_write" : "html_create";
                messages.Add(new ChatMessage { Role = "user",
                    Content = $"document_plan이 완료되었습니다. " +
                              $"위 결과의 body/sections의 [내용...] 부분을 실제 상세 내용으로 모두 채워서 " +
                              $"{toolHint} 도구를 지금 즉시 호출하세요. " +
                              $"각 섹션마다 반드시 충분한 내용을 작성하고, 설명 없이 도구를 바로 호출하세요." });
                EmitEvent(AgentEventType.Thinking, "", $"문서 개요 완성 — {toolHint} 호출 중...");
            }

            // 단일 문서 생성 도구 성공 → 즉시 루프 종료
            if (result.Success && IsTerminalDocumentTool(toolName17) && toolCalls.Count == 1)
            {
                var shouldVerify2 = activeTabSnapshot == "Code"
                    ? llm.Code.EnableCodeVerification && IsCodeVerificationTarget(toolName17)
                    : llm.EnableCoworkVerification && IsDocumentCreationTool(toolName17);
                if (shouldVerify2)
                {
                    await RunPostToolVerificationAsync(messages, toolName17, result, context, ct);
                }
                EmitEvent(AgentEventType.Complete, "", "에이전트 작업 완료");
                return (ToolCallAction.Return, result.Output ?? "");
            }

            // Post-Tool Verification: 탭별 검증 강제
            var shouldVerify = activeTabSnapshot == "Code"
                ? llm.Code.EnableCodeVerification && IsCodeVerificationTarget(toolName17)
                : llm.EnableCoworkVerification && IsDocumentCreationTool(toolName17);
            if (shouldVerify && result.Success)
            {
                await RunPostToolVerificationAsync(messages, toolName17, result, context, ct);
            }
        }

        return (ToolCallAction.Continue, null);
    }

    /// <summary>도구 호출 처리 후 루프 제어 액션.</summary>
    private enum ToolCallAction { Continue, Break, Return }

    /// <summary>Phase 33-B / 17-C: 도구 훅(Pre/Post/Failure) 실행 헬퍼.</summary>
    private async Task RunToolHooksAsync(
        Models.LlmSettings llm, LlmService.ContentBlock call,
        AgentContext context, string phase, CancellationToken ct,
        ToolResult? result = null)
    {
        // ── 레거시 AgentHooks (command 스크립트, 하위 호환) ──────────────
        if (llm.EnableToolHooks && llm.AgentHooks.Count > 0)
        {
            try
            {
                var hookResults = await AgentHookRunner.RunAsync(
                    llm.AgentHooks, call.ToolName, phase,
                    toolInput: call.ToolInput.ToString(),
                    toolOutput: result != null ? TruncateOutput(result.Output, 2048) : null,
                    success: result?.Success ?? false,
                    workFolder: context.WorkFolder,
                    timeoutMs: llm.ToolHookTimeoutMs,
                    ct: ct);
                foreach (var pr in hookResults.Where(r => !r.Success))
                    EmitEvent(AgentEventType.Error, call.ToolName, $"[Hook:{pr.HookName}] {pr.Output}");
            }
            catch (Exception ex) { LogService.Warn($"[AgentLoop] {phase}ToolUse 레거시 훅 실패: {ex.Message}"); }
        }

        // ── Phase 17-C: 확장 훅 (ExtendedHooks — command/http/prompt 모드) ──
        var extKind = phase == "pre"
            ? HookEventKind.PreToolUse
            : (result?.Success == false ? HookEventKind.PostToolUseFailure : HookEventKind.PostToolUse);

        await RunExtendedEventAsync(
            extKind, null, ct,
            toolName:   call.ToolName,
            toolInput:  call.ToolInput?.ToString(),
            toolOutput: result != null ? TruncateOutput(result.Output, 2048) : null,
            toolSuccess: result?.Success ?? true);
    }

    /// <summary>DelegateAgentTool에서 호출하는 서브에이전트 실행기.</summary>
    private async Task<string> RunSubAgentAsync(string agentType, string task, string workFolder, CancellationToken ct)
    {
        try
        {
            var systemPrompt = $"당신은 전문 {agentType} 에이전트입니다. 주어진 작업을 수행하세요.";
            var msgs = new List<ChatMessage>
            {
                new() { Role = "system", Content = systemPrompt },
                new() { Role = "user", Content = task }
            };

            // 서브에이전트는 간단한 텍스트 응답으로 처리 (도구 호출 없이)
            var response = await _llm.SendAsync(msgs, ct);
            return response;
        }
        catch (Exception ex)
        {
            return $"서브에이전트 실행 오류: {ex.Message}";
        }
    }

    /// <summary>읽기 전용 도구들을 병렬 실행합니다.</summary>
    private async Task ExecuteToolsInParallelAsync(
        List<LlmService.ContentBlock> calls,
        List<ChatMessage> messages,
        AgentContext context,
        List<string> planSteps,
        ParallelState state,
        int baseMax, int maxRetry,
        Models.LlmSettings llm,
        int iteration,
        CancellationToken ct,
        List<string> statsUsedTools)
    {
        EmitEvent(AgentEventType.Thinking, "", $"읽기 전용 도구 {calls.Count}개를 병렬 실행 중...");

        var tasks = calls.Select(async call =>
        {
            var tool = _tools.Get(call.ToolName);
            if (tool == null)
                return (call, ToolResult.Fail($"알 수 없는 도구: {call.ToolName}"), 0L);

            var sw = System.Diagnostics.Stopwatch.StartNew();
            try
            {
                var input = call.ToolInput ?? JsonDocument.Parse("{}").RootElement;
                var result = await tool.ExecuteAsync(input, context, ct);
                sw.Stop();
                return (call, result, sw.ElapsedMilliseconds);
            }
            catch (Exception ex)
            {
                sw.Stop();
                return (call, ToolResult.Fail($"도구 실행 오류: {ex.Message}"), sw.ElapsedMilliseconds);
            }
        }).ToList();

        var results = await Task.WhenAll(tasks);

        // 결과를 순서대로 메시지에 추가
        foreach (var (call, result, elapsed) in results)
        {
            EmitEvent(
                result.Success ? AgentEventType.ToolResult : AgentEventType.Error,
                call.ToolName,
                TruncateOutput(result.Output, 200),
                result.FilePath,
                elapsedMs: elapsed,
                iteration: iteration);

            if (result.Success) state.StatsSuccessCount++; else state.StatsFailCount++;
            if (!statsUsedTools.Contains(call.ToolName))
                statsUsedTools.Add(call.ToolName);

            state.TotalToolCalls++;
            messages.Add(LlmService.CreateToolResultMessage(
                call.ToolId, call.ToolName, TruncateOutput(result.Output, Defaults.ToolResultTruncateLength)));

            if (!result.Success)
            {
                state.ConsecutiveErrors++;
                if (state.ConsecutiveErrors > maxRetry)
                {
                    messages.Add(LlmService.CreateToolResultMessage(
                        call.ToolId, call.ToolName,
                        $"[FAILED after retries] {TruncateOutput(result.Output, 500)}"));
                }
            }
            else
            {
                state.ConsecutiveErrors = 0;
            }

            // 감사 로그
            if (llm.EnableAuditLog)
            {
                AuditLogService.LogToolCall(
                    _conversationId, context.ActiveTab ?? "",
                    call.ToolName,
                    call.ToolInput?.ToString() ?? "",
                    TruncateOutput(result.Output, 500),
                    result.FilePath, result.Success);
            }
        }
    }
}
