using AxCopilot.Models;

namespace AxCopilot.Services.Agent;

/// <summary>
/// 컨텍스트 윈도우 관리: 대화가 길어지면 이전 메시지를 요약하여 압축합니다.
/// MemGPT/OpenHands의 LLMSummarizingCondenser 패턴을 경량 구현.
///
/// 2단계 압축 전략:
///  1단계 — 도구 결과 자르기: 대용량 tool_result 출력을 핵심만 남기고 축약 (LLM 호출 없음)
///  2단계 — 이전 대화 요약: 오래된 메시지를 LLM으로 요약하여 교체 (LLM 1회 호출)
/// </summary>
public static class ContextCondenser
{
    /// <summary>도구 결과 1개당 최대 유지 길이 (자)</summary>
    private const int MaxToolResultChars = 1500;

    /// <summary>요약 시 유지할 최근 메시지 수</summary>
    private const int RecentKeepCount = 6;

    /// <summary>모델별 입력 토큰 한도 (대략). 정확한 값은 중요하지 않음 — 안전 마진으로 70% 적용.</summary>
    private static int GetModelInputLimit(string service, string model)
    {
        var key = $"{service}:{model}".ToLowerInvariant();
        return key switch
        {
            _ when key.Contains("claude") => 180_000,    // Claude 200K
            _ when key.Contains("gemini-2.5") => 900_000, // Gemini 1M
            _ when key.Contains("gemini-2.0") => 900_000,
            _ when key.Contains("gemini") => 900_000,
            _ when key.Contains("gpt-4") => 120_000,      // GPT-4 128K
            _ => 16_000, // Ollama/vLLM 로컬 모델 기본값
        };
    }

    /// <summary>
    /// 메시지 목록의 토큰이 모델 한도에 근접하면 자동 압축합니다.
    /// 1단계: 도구 결과 축약 (빠르고 LLM 호출 없음)
    /// 2단계: 이전 대화 LLM 요약 (토큰이 여전히 높으면)
    /// </summary>
    public static async Task<bool> CondenseIfNeededAsync(
        List<ChatMessage> messages, LlmService llm, int maxOutputTokens, CancellationToken ct = default)
    {
        if (messages.Count < 6) return false;

        // 현재 모델의 입력 토큰 한도
        var settings = llm.GetCurrentModelInfo();
        var inputLimit = GetModelInputLimit(settings.service, settings.model);
        var threshold = (int)(inputLimit * 0.65); // 65%에서 압축 시작

        var currentTokens = TokenEstimator.EstimateMessages(messages);
        if (currentTokens < threshold) return false;

        bool didCompress = false;

        // ── 1단계: 도구 결과 축약 (LLM 호출 없음, 즉시 실행) ──
        didCompress |= TruncateToolResults(messages);

        // 1단계 후 다시 추정
        currentTokens = TokenEstimator.EstimateMessages(messages);
        if (currentTokens < threshold) return didCompress;

        // ── 2단계: 이전 대화 LLM 요약 ──
        didCompress |= await SummarizeOldMessagesAsync(messages, llm, ct);

        if (didCompress)
        {
            var afterTokens = TokenEstimator.EstimateMessages(messages);
            LogService.Info($"Context Condenser: {currentTokens} → {afterTokens} 토큰 (절감 {currentTokens - afterTokens})");
        }

        return didCompress;
    }

    /// <summary>
    /// 1단계: 대용량 도구 결과를 축약합니다.
    /// tool_result JSON이나 긴 파일 내용 등을 핵심만 남기고 자릅니다.
    /// </summary>
    private static bool TruncateToolResults(List<ChatMessage> messages)
    {
        bool truncated = false;

        // 최근 RecentKeepCount개는 건드리지 않음 (방금 실행한 도구 결과는 유지)
        var cutoff = Math.Max(0, messages.Count - RecentKeepCount);

        for (int i = 0; i < cutoff; i++)
        {
            var msg = messages[i];
            if (msg.Content == null) continue;

            // tool_result 메시지의 대용량 출력 축약
            if (msg.Content.StartsWith("{\"type\":\"tool_result\"") && msg.Content.Length > MaxToolResultChars)
            {
                // JSON 구조를 유지하되 output 부분만 축약
                messages[i] = new ChatMessage
                {
                    Role = msg.Role,
                    Content = TruncateToolResultJson(msg.Content),
                    Timestamp = msg.Timestamp,
                };
                truncated = true;
            }
            // assistant의 도구 호출 블록 내 긴 텍스트도 축약
            else if (msg.Role == "assistant" && msg.Content.Length > MaxToolResultChars * 2 &&
                     msg.Content.StartsWith("{\"_tool_use_blocks\""))
            {
                // 도구 호출 구조는 유지, 텍스트 블록만 축약
                if (msg.Content.Length > MaxToolResultChars * 3)
                {
                    messages[i] = new ChatMessage
                    {
                        Role = msg.Role,
                        Content = msg.Content[..(MaxToolResultChars * 2)] + "...[축약됨]\"]}",
                        Timestamp = msg.Timestamp,
                    };
                    truncated = true;
                }
            }
            // 일반 assistant/user 메시지 중 비정상적으로 긴 것 (예: 파일 내용 전체 붙여넣기)
            else if (msg.Content.Length > MaxToolResultChars * 3 && msg.Role != "system")
            {
                messages[i] = new ChatMessage
                {
                    Role = msg.Role,
                    Content = msg.Content[..MaxToolResultChars] + "\n\n...[이전 내용 축약됨 — 원본 " +
                              $"{msg.Content.Length:N0}자 중 {MaxToolResultChars:N0}자 유지]",
                    Timestamp = msg.Timestamp,
                };
                truncated = true;
            }
        }

        return truncated;
    }

    /// <summary>tool_result JSON 내의 output 값을 축약합니다.</summary>
    private static string TruncateToolResultJson(string json)
    {
        // 간단한 문자열 처리로 output 부분만 축약 (JSON 파서 없이)
        const string marker = "\"output\":\"";
        var idx = json.IndexOf(marker, StringComparison.Ordinal);
        if (idx < 0) return json[..Math.Min(json.Length, MaxToolResultChars)] + "...[축약됨]}";

        var outputStart = idx + marker.Length;
        // output 끝 찾기 (이스케이프된 따옴표 고려)
        var outputEnd = outputStart;
        while (outputEnd < json.Length)
        {
            if (json[outputEnd] == '\\') { outputEnd += 2; continue; }
            if (json[outputEnd] == '"') break;
            outputEnd++;
        }

        var outputLen = outputEnd - outputStart;
        if (outputLen <= MaxToolResultChars) return json; // 이미 짧음

        // 앞부분 + "...[축약됨]" + 뒷부분
        var keepLen = MaxToolResultChars / 2;
        var prefix = json[..outputStart];
        var outputText = json[outputStart..outputEnd];
        var suffix = json[outputEnd..];

        return prefix +
               outputText[..keepLen] +
               "\\n...[축약됨: " + $"{outputLen:N0}" + "자 중 " + $"{MaxToolResultChars:N0}" + "자 유지]\\n" +
               outputText[^keepLen..] +
               suffix;
    }

    /// <summary>
    /// 2단계: 오래된 메시지를 LLM으로 요약합니다.
    /// 시스템 메시지 + 최근 N개는 유지하고, 나머지를 요약으로 교체합니다.
    /// </summary>
    private static async Task<bool> SummarizeOldMessagesAsync(
        List<ChatMessage> messages, LlmService llm, CancellationToken ct)
    {
        var systemMsg = messages.FirstOrDefault(m => m.Role == "system");
        var systemCount = systemMsg != null ? 1 : 0;
        var nonSystemMessages = messages.Where(m => m.Role != "system").ToList();

        var keepCount = Math.Min(RecentKeepCount, nonSystemMessages.Count);
        var recentMessages = nonSystemMessages.Skip(nonSystemMessages.Count - keepCount).ToList();
        var oldMessages = nonSystemMessages.Take(nonSystemMessages.Count - keepCount).ToList();

        if (oldMessages.Count < 3) return false;

        // 요약 대상 텍스트 구성
        var sb = new System.Text.StringBuilder();
        sb.AppendLine("다음 대화 기록을 간결하게 요약하세요.");
        sb.AppendLine("반드시 유지할 정보: 사용자 요청, 핵심 결정 사항, 생성/수정된 파일 경로, 작업 진행 상황, 중요한 결과.");
        sb.AppendLine("제거할 정보: 도구 실행의 상세 출력, 반복되는 내용, 중간 사고 과정.");
        sb.AppendLine("요약은 대화와 동일한 언어로 작성하세요. 글머리 기호(-)로 핵심 사항만 나열하세요.\n---");

        foreach (var m in oldMessages)
        {
            var content = m.Content ?? "";
            if (content.StartsWith("{\"_tool_use_blocks\""))
                content = "[도구 호출]";
            else if (content.StartsWith("{\"type\":\"tool_result\""))
                content = "[도구 결과]";
            else if (content.Length > 300)
                content = content[..300] + "...";

            sb.AppendLine($"[{m.Role}]: {content}");
        }

        try
        {
            var summaryMessages = new List<ChatMessage>
            {
                new() { Role = "user", Content = sb.ToString() }
            };

            var summary = await llm.SendAsync(summaryMessages, ct);
            if (string.IsNullOrEmpty(summary)) return false;

            // 메시지 재구성: system + 요약 + 최근 메시지
            messages.Clear();
            if (systemMsg != null) messages.Add(systemMsg);

            messages.Add(new ChatMessage
            {
                Role = "user",
                Content = $"[이전 대화 요약 — {oldMessages.Count}개 메시지 압축]\n{summary}",
                Timestamp = DateTime.Now,
            });
            messages.Add(new ChatMessage
            {
                Role = "assistant",
                Content = "이전 대화 내용을 확인했습니다. 이어서 작업하겠습니다.",
                Timestamp = DateTime.Now,
            });

            messages.AddRange(recentMessages);
            return true;
        }
        catch (Exception ex)
        {
            LogService.Warn($"Context Condenser 요약 실패: {ex.Message}");
            return false;
        }
    }
}
