using System.Text;
using System.Text.Json;
using ClawdDotNet.Core.Memory;
using ClawdDotNet.Core.Tools;
using Microsoft.Extensions.Logging;

namespace ClawdDotNet.Tools.Memory;

/// <summary>
/// Langzeitgedächtnis für Agenten.
///
/// Ohne dieses Tool beginnt ein geplanter Agent bei jedem Cron-Lauf bei null: Er ruft
/// dieselben Quellen ab, zieht dieselben Schlüsse und kann keine Entwicklung über die
/// Zeit verfolgen. Das ist zugleich die größte Fähigkeitslücke und eine dauerhafte
/// Token-Verschwendung.
/// </summary>
public sealed class MemoryTool : IAgentTool
{
    /// <summary>Obergrenze für die Ausgabe, damit ein Abruf den Kontext nicht sprengt.</summary>
    private const int MaxResultChars = 8_000;

    public string Name => "Memory";

    public string Description => """
        Dein Langzeitgedächtnis — überdauert einzelne Läufe und Neustarts.

        Nutze es, um Erkenntnisse festzuhalten, die beim nächsten Lauf noch zählen:
        getroffene Entscheidungen, gesicherte Fakten, offene Punkte, Beobachtungen
        über Zeit. Prüfe zu Beginn eines Laufs mit 'recall', was du bereits weißt,
        statt es erneut herzuleiten.

        Aktionen: remember, recall, forget, list_subjects

        Zwei Bereiche:
        - scope='agent' (Standard): nur für dich sichtbar
        - scope='shared': für alle Agenten der Instanz sichtbar

        Zum 'key': Vergib einen, wenn eine Angabe sich später ändern kann
        (z.B. key='kursziel_nvda'). Erneutes Merken unter demselben Schlüssel
        aktualisiert den Eintrag, statt einen zweiten anzulegen — so wächst dein
        Gedächtnis nicht zu.
        """;

    public JsonElement InputSchema { get; } = JsonDocument.Parse("""
        {
          "type": "object",
          "properties": {
            "action": {
              "type": "string",
              "enum": ["remember", "recall", "forget", "list_subjects"],
              "description": "Die auszuführende Aktion."
            },
            "scope": {
              "type": "string",
              "enum": ["agent", "shared"],
              "description": "Bereich. Standard ist 'agent' (nur für dich)."
            },
            "subject": {
              "type": "string",
              "description": "Worum es geht — z.B. 'NVDA', 'Kunde Meier', 'Projekt Alpha'. Pflicht bei remember."
            },
            "content": {
              "type": "string",
              "description": "Was du dir merken willst. Pflicht bei remember."
            },
            "key": {
              "type": "string",
              "description": "Optionaler eindeutiger Schlüssel. Erneutes Merken darunter aktualisiert den Eintrag."
            },
            "category": {
              "type": "string",
              "enum": ["fact", "decision", "observation", "task", "contact", "other"],
              "description": "Art der Erinnerung. Standard 'observation'."
            },
            "tags": {
              "type": "array",
              "items": { "type": "string" },
              "description": "Schlagworte zum späteren Wiederfinden."
            },
            "importance": {
              "type": "integer",
              "description": "1 (nebensächlich) bis 5 (zentral). Standard 3. Steuert die Reihenfolge beim Abruf."
            },
            "query": {
              "type": "string",
              "description": "Suchtext für recall — wird gegen Betreff, Inhalt und Schlagworte geprüft."
            },
            "limit": {
              "type": "integer",
              "description": "Maximale Anzahl Treffer bei recall. Standard 20."
            },
            "minImportance": {
              "type": "integer",
              "description": "Nur Erinnerungen ab dieser Wichtigkeit abrufen."
            },
            "id": {
              "type": "integer",
              "description": "Id der zu löschenden Erinnerung (forget)."
            }
          },
          "required": ["action"]
        }
        """).RootElement.Clone();

    public async Task<ToolResult> ExecuteAsync(
        JsonElement input, AgentToolContext context, CancellationToken ct)
    {
        if (context.Memory is not { } repository)
            return ToolResult.Fail("Das Gedächtnis ist für diese Instanz nicht verfügbar.");

        var action = input.TryGetProperty("action", out var a) ? a.GetString() : null;
        if (string.IsNullOrWhiteSpace(action))
            return ToolResult.Fail("'action' ist erforderlich.");

        try
        {
            return action switch
            {
                "remember" => await RememberAsync(input, repository, context, ct),
                "recall" => await RecallAsync(input, repository, context, ct),
                "forget" => await ForgetAsync(input, repository, context, ct),
                "list_subjects" => await ListSubjectsAsync(input, repository, context, ct),
                _ => ToolResult.Fail($"Unbekannte Aktion: {action}")
            };
        }
        catch (Exception ex)
        {
            context.Logger.LogError(ex, "Fehler im Memory-Tool bei Aktion {Action}", action);
            return ToolResult.Fail($"Fehler: {ex.Message}");
        }
    }

    // ─── Aktionen ───

    private static async Task<ToolResult> RememberAsync(
        JsonElement input, IMemoryRepository repository, AgentToolContext context, CancellationToken ct)
    {
        var subject = GetString(input, "subject");
        if (string.IsNullOrWhiteSpace(subject))
            return ToolResult.Fail("'subject' ist erforderlich — worum geht es?");

        var content = GetString(input, "content");
        if (string.IsNullOrWhiteSpace(content))
            return ToolResult.Fail("'content' ist erforderlich — was soll gemerkt werden?");

        var scope = GetScope(input);

        var entry = new MemoryEntry
        {
            Scope = scope,
            OwnerId = context.AgentId,
            Subject = subject.Trim(),
            Content = content.Trim(),
            Key = GetString(input, "key"),
            Category = MemoryCategory.Normalize(GetString(input, "category") ?? MemoryCategory.Observation),
            Tags = GetTags(input),
            Importance = GetInt(input, "importance") ?? 3,
            CreatedBy = context.AgentId
        };

        var saved = await repository.RememberAsync(entry, ct);

        var scopeLabel = scope == MemoryScope.Shared ? "geteilt" : "privat";
        var keyHint = string.IsNullOrWhiteSpace(saved.Key) ? "" : $" | Schlüssel: {saved.Key}";

        return ToolResult.Ok(
            $"Gemerkt (#{saved.Id}, {scopeLabel}){keyHint}\n" +
            $"Betreff: {saved.Subject} | Art: {saved.Category} | Wichtigkeit: {saved.Importance}");
    }

    private static async Task<ToolResult> RecallAsync(
        JsonElement input, IMemoryRepository repository, AgentToolContext context, CancellationToken ct)
    {
        var scope = GetScope(input);

        var query = new MemoryQuery
        {
            Scope = scope,
            OwnerId = context.AgentId,
            Search = GetString(input, "query"),
            Subject = GetString(input, "subject"),
            Category = GetString(input, "category"),
            Tags = GetTags(input),
            Limit = GetInt(input, "limit") ?? 20,
            MinImportance = GetInt(input, "minImportance") ?? 1
        };

        var entries = await repository.RecallAsync(query, ct);

        if (entries.Count == 0)
        {
            var total = await repository.CountAsync(scope, context.AgentId, ct);
            return ToolResult.Ok(total == 0
                ? "Noch keine Erinnerungen in diesem Bereich."
                : $"Keine Treffer. Der Bereich enthält {total} Erinnerung(en) — " +
                  "versuche einen anderen Suchbegriff oder 'list_subjects' für einen Überblick.");
        }

        return ToolResult.Ok(Render(entries, scope));
    }

    private static async Task<ToolResult> ForgetAsync(
        JsonElement input, IMemoryRepository repository, AgentToolContext context, CancellationToken ct)
    {
        if (GetInt(input, "id") is { } id)
        {
            var removed = await repository.ForgetAsync(id, ct);
            return ToolResult.Ok(removed
                ? $"Erinnerung #{id} gelöscht."
                : $"Keine Erinnerung mit der Id {id} gefunden.");
        }

        var subject = GetString(input, "subject");
        if (string.IsNullOrWhiteSpace(subject))
            return ToolResult.Fail("Für 'forget' wird entweder 'id' oder 'subject' benötigt.");

        var count = await repository.ForgetBySubjectAsync(GetScope(input), context.AgentId, subject, ct);
        return ToolResult.Ok($"{count} Erinnerung(en) zum Betreff '{subject}' gelöscht.");
    }

    private static async Task<ToolResult> ListSubjectsAsync(
        JsonElement input, IMemoryRepository repository, AgentToolContext context, CancellationToken ct)
    {
        var scope = GetScope(input);
        var subjects = await repository.ListSubjectsAsync(
            scope, context.AgentId, GetInt(input, "limit") ?? 50, ct);

        if (subjects.Count == 0)
            return ToolResult.Ok("Noch keine Erinnerungen in diesem Bereich.");

        var sb = new StringBuilder();
        sb.AppendLine($"Betreffs im Bereich '{(scope == MemoryScope.Shared ? "shared" : "agent")}':");
        foreach (var (subject, count) in subjects)
            sb.AppendLine($"  • {subject} ({count})");

        return ToolResult.Ok(sb.ToString().TrimEnd());
    }

    // ─── Darstellung ───

    private static string Render(IReadOnlyList<MemoryEntry> entries, MemoryScope scope)
    {
        var sb = new StringBuilder();
        sb.AppendLine($"{entries.Count} Erinnerung(en), wichtigste zuerst:");
        sb.AppendLine();

        var shown = 0;
        foreach (var entry in entries)
        {
            var block = FormatEntry(entry, scope);

            // Lieber weniger Treffer als ein gesprengter Kontext.
            if (sb.Length + block.Length > MaxResultChars)
            {
                sb.AppendLine($"[… {entries.Count - shown} weitere Treffer ausgelassen. " +
                              "Grenze die Suche ein, um sie zu sehen.]");
                break;
            }

            sb.Append(block);
            shown++;
        }

        return sb.ToString().TrimEnd();
    }

    private static string FormatEntry(MemoryEntry entry, MemoryScope scope)
    {
        var sb = new StringBuilder();

        sb.Append($"#{entry.Id} [{entry.Subject}] {entry.Category}, Wichtigkeit {entry.Importance}");
        if (!string.IsNullOrWhiteSpace(entry.Key))
            sb.Append($", Schlüssel: {entry.Key}");
        sb.AppendLine();

        sb.AppendLine($"  {entry.Content}");

        var meta = new List<string> { $"aktualisiert {entry.UpdatedAt:yyyy-MM-dd HH:mm} UTC" };
        if (entry.Tags.Count > 0)
            meta.Add("Schlagworte: " + string.Join(", ", entry.Tags));
        if (scope == MemoryScope.Shared && !string.IsNullOrWhiteSpace(entry.CreatedBy))
            meta.Add($"von {entry.CreatedBy}");

        sb.AppendLine($"  ({string.Join(" | ", meta)})");
        sb.AppendLine();

        return sb.ToString();
    }

    // ─── Eingabe lesen ───

    private static string? GetString(JsonElement input, string name)
        => input.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.String
            ? value.GetString()
            : null;

    private static int? GetInt(JsonElement input, string name)
        => input.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.Number
            ? value.GetInt32()
            : null;

    private static MemoryScope GetScope(JsonElement input)
        => string.Equals(GetString(input, "scope"), "shared", StringComparison.OrdinalIgnoreCase)
            ? MemoryScope.Shared
            : MemoryScope.Agent;

    private static IReadOnlyList<string> GetTags(JsonElement input)
    {
        if (!input.TryGetProperty("tags", out var tags) || tags.ValueKind != JsonValueKind.Array)
            return [];

        return tags.EnumerateArray()
            .Where(t => t.ValueKind == JsonValueKind.String)
            .Select(t => t.GetString()!)
            .Where(t => !string.IsNullOrWhiteSpace(t))
            .ToList();
    }
}
