using ClawdDotNet.Core.Api;
using ClawdDotNet.Core.Api.Models;
using ClawdDotNet.Core.Config;
using Microsoft.Extensions.Logging;

namespace ClawdDotNet.Core.Engine;

public sealed class ContextCompactor
{
    private readonly IChatCompletionClient _client;
    private readonly ILogger _logger;

    private const int ProtectedTailMessages = 6;
    private const int MaxToolResultChars = 2000;
    private const string TruncatedMarker = "\n\n[... Ergebnis gekürzt ...]";

    public ContextCompactor(IChatCompletionClient client, ILoggerFactory loggerFactory)
    {
        _client = client;
        _logger = loggerFactory.CreateLogger("ClawdDotNet.Core.Engine.ContextCompactor");
    }

    public static int EstimateTokens(List<ChatMessage> messages)
    {
        var totalChars = 0;
        foreach (var msg in messages)
        {
            totalChars += msg.Content?.Length ?? 0;
            totalChars += msg.Role.Length + 10;

            if (msg.ToolCalls is not null)
            {
                foreach (var tc in msg.ToolCalls)
                    totalChars += tc.Function.Name.Length + tc.Function.Arguments.Length + 20;
            }
        }
        return totalChars / 4;
    }

    public async Task<bool> CompactIfNeededAsync(
        List<ChatMessage> messages,
        int lastPromptTokens,
        LoopGuardConfig guard,
        string model,
        CancellationToken ct)
    {
        var contextTokens = lastPromptTokens > 0
            ? lastPromptTokens
            : EstimateTokens(messages);

        var threshold = (int)(guard.MaxContextTokens * guard.CompactionThreshold);

        if (contextTokens < threshold)
            return false;

        _logger.LogInformation(
            "Context kompaktierung gestartet: {Tokens} Tokens (Schwelle: {Threshold})",
            contextTokens, threshold);

        // Stufe 1: Tool-Results kürzen
        var pruned = PruneToolResults(messages);
        if (pruned)
        {
            var afterPrune = EstimateTokens(messages);
            _logger.LogInformation("Stufe 1 (Tool-Pruning): {Before} → {After} geschätzte Tokens",
                contextTokens, afterPrune);

            if (afterPrune < threshold)
                return true;
        }

        // Stufe 2: Auto-Compaction via LLM — bewusst mit dem günstigen Modell.
        var summaryModel = string.IsNullOrWhiteSpace(guard.SummaryModel)
            ? model
            : guard.SummaryModel;

        await CompactViaLlmAsync(messages, summaryModel, ct);
        return true;
    }

    private bool PruneToolResults(List<ChatMessage> messages)
    {
        var pruned = false;
        var protectedStart = Math.Max(0, messages.Count - ProtectedTailMessages);

        for (var i = 0; i < protectedStart; i++)
        {
            var msg = messages[i];
            if (msg.Role != "tool" || msg.Content is null)
                continue;

            if (msg.Content.Length <= MaxToolResultChars)
                continue;

            msg.Content = msg.Content[..MaxToolResultChars] + TruncatedMarker;
            pruned = true;
        }

        return pruned;
    }

    /// <summary>
    /// Sucht einen Index, an dem der Kontext geteilt werden darf, ohne eine Tool-Sequenz
    /// zu zerreißen.
    ///
    /// Eine <c>tool</c>-Nachricht darf nie am Anfang des behaltenen Teils stehen — ihr
    /// <c>assistant</c>-Aufruf stünde sonst nicht mehr davor und die API lehnt die
    /// gesamte Anfrage mit HTTP 400 ab. Deshalb wandert die Grenze rückwärts bis zum
    /// Beginn des Blocks, in den sie fällt.
    /// </summary>
    private static int FindSafeTailStart(List<ChatMessage> messages, int desiredStart, int minIndex)
    {
        var i = Math.Clamp(desiredStart, minIndex, messages.Count);

        // Rückwärts, bis der Anfang keine verwaiste Tool-Antwort mehr ist.
        while (i > minIndex && i < messages.Count && messages[i].Role == "tool")
            i--;

        return i;
    }

    private async Task CompactViaLlmAsync(
        List<ChatMessage> messages, string model, CancellationToken ct)
    {
        var systemMsg = messages.FirstOrDefault(m => m.Role == "system");

        // Der System-Prompt wird separat behandelt und darf nie Teil des Tails werden,
        // sonst landet er doppelt im Kontext.
        var firstNonSystem = systemMsg is null ? 0 : messages.IndexOf(systemMsg) + 1;

        var tailStart = FindSafeTailStart(
            messages,
            messages.Count - ProtectedTailMessages,
            firstNonSystem);

        // Wenn vor dem geschützten Ende nichts Nennenswertes liegt, gibt es nichts
        // zusammenzufassen — eine Kompaktierung würde den Kontext sogar vergrößern.
        if (tailStart - firstNonSystem < 2)
        {
            _logger.LogInformation(
                "Compaction übersprungen: nur {Count} Nachricht(en) vor dem geschützten Ende",
                tailStart - firstNonSystem);
            return;
        }

        // Nur den Teil zusammenfassen, der tatsächlich wegfällt — der Tail bleibt
        // ohnehin wörtlich erhalten und müsste sonst doppelt bezahlt werden.
        var conversationParts = messages
            .Take(tailStart)
            .Skip(firstNonSystem)
            .Select(FormatMessageForSummary);

        var conversationText = string.Join("\n", conversationParts);

        // Auf max 30k Zeichen begrenzen für den Summarization-Call
        if (conversationText.Length > 30_000)
            conversationText = conversationText[..30_000] + "\n[... weitere Nachrichten ausgelassen ...]";

        var summaryRequest = new ChatRequest
        {
            Model = model,
            Messages =
            [
                ChatMessage.System(
                    "Du bist ein Konversations-Zusammenfasser. Erstelle eine präzise Zusammenfassung " +
                    "der bisherigen Konversation. Behalte alle wichtigen Fakten, Entscheidungen, " +
                    "Ergebnisse von Tool-Aufrufen und den aktuellen Arbeitsstand bei. " +
                    "Schreibe in der dritten Person. Format: Strukturierte Stichpunkte."),
                ChatMessage.User(
                    "Fasse die folgende Konversation zusammen. Behalte alle wichtigen Details:\n\n" +
                    conversationText)
            ]
        };

        try
        {
            var response = await _client.CompleteAsync(summaryRequest, ct);
            var summary = response.Choices.FirstOrDefault()?.Message?.Content;

            if (string.IsNullOrWhiteSpace(summary))
            {
                _logger.LogWarning("Compaction: Keine Zusammenfassung erhalten");
                return;
            }

            // Nachrichten ersetzen: System-Prompt + Zusammenfassung + geschützte letzte Nachrichten.
            // tailStart liegt garantiert auf einer Blockgrenze und hinter dem System-Prompt.
            var tail = messages.Skip(tailStart).ToList();

            messages.Clear();

            if (systemMsg is not null)
                messages.Add(systemMsg);

            messages.Add(ChatMessage.User(
                "[Zusammenfassung der bisherigen Konversation]\n\n" + summary));
            messages.Add(ChatMessage.Assistant(
                "Verstanden. Ich habe den Kontext der bisherigen Konversation erfasst und arbeite weiter."));

            messages.AddRange(tail);

            _logger.LogInformation(
                "Stufe 2 (Auto-Compaction): Konversation auf {Count} Nachrichten kompaktiert",
                messages.Count);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Compaction fehlgeschlagen");
        }
    }

    private static string FormatMessageForSummary(ChatMessage msg)
    {
        if (msg.Role == "tool")
        {
            var preview = msg.Content?.Length > 200
                ? msg.Content[..200] + "..."
                : msg.Content;
            return $"[Tool-Result ({msg.ToolCallId})]: {preview}";
        }

        if (msg.ToolCalls is { Count: > 0 })
        {
            var calls = string.Join(", ",
                msg.ToolCalls.Select(tc => $"{tc.Function.Name}({tc.Function.Arguments[..Math.Min(100, tc.Function.Arguments.Length)]})"));
            return $"[Assistant → Tool-Calls]: {calls}";
        }

        var content = msg.Content?.Length > 500
            ? msg.Content[..500] + "..."
            : msg.Content;

        return $"[{msg.Role}]: {content}";
    }
}
