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

namespace ClawdDotNet.Tools.AgentEditor;

/// <summary>
/// Ermöglicht einem Agent, die Identity- und Soul-Dateien anderer Agenten in der Instanz
/// zu lesen, zu bearbeiten und neue Agenten anzulegen.
///
/// Sicherheitskonzept:
/// - Der Agent kann nur Soul.md und Identity.md lesen/schreiben (keine AgentSettings, keine Chat-Daten)
/// - Neue Agenten bekommen nur die Grundstruktur — Tool-Zuweisung etc. bleibt dem Benutzer vorbehalten
/// - Alle Änderungen werden mit Backup gesichert (.bak-Datei)
/// </summary>
public sealed class AgentEditorTool : IAgentTool
{
    public string Name => "AgentEditor";

    public string Description => """
        Verwaltet die Agenten-Persönlichkeiten in der Instanz. Ermöglicht das Lesen und Bearbeiten
        von Identity (Rolle, Expertise) und Soul (Persönlichkeit, Arbeitsweise, Werte) aller Agenten.

        Aktionen:
        - list_agents: Übersicht aller Agenten mit Namen, Beschreibung und verfügbaren Dateien
        - read_identity: Identity.md eines Agenten lesen (WER ist der Agent)
        - read_soul: Soul.md eines Agenten lesen (WIE denkt/arbeitet der Agent)
        - update_identity: Identity.md eines Agenten aktualisieren (erstellt Backup)
        - update_soul: Soul.md eines Agenten aktualisieren (erstellt Backup)
        - create_agent: Einen neuen Agenten in der Instanz erstellen (Grundstruktur mit Identity und Soul)

        Wichtig: Verschaffe dir IMMER zuerst mit list_agents eine Übersicht, bevor du Änderungen vornimmst.
        Lies auch die bestehende Identity/Soul bevor du sie überschreibst, um nichts Wichtiges zu verlieren.
        """;

    public JsonElement InputSchema { get; } = JsonDocument.Parse("""
        {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["list_agents", "read_identity", "read_soul", "update_identity", "update_soul", "create_agent"],
                    "description": "Die auszuführende Aktion"
                },
                "agentId": {
                    "type": "string",
                    "description": "Die ID des Ziel-Agenten (z.B. 'senior_developer'). Nicht erforderlich für list_agents."
                },
                "content": {
                    "type": "string",
                    "description": "Der neue Inhalt für Identity oder Soul (nur für update_identity/update_soul)"
                },
                "agentName": {
                    "type": "string",
                    "description": "Der Anzeigename für einen neuen Agenten (nur für create_agent, z.B. 'News Scanner')"
                },
                "agentDescription": {
                    "type": "string",
                    "description": "Kurzbeschreibung der Rolle des neuen Agenten (nur für create_agent)"
                },
                "identity": {
                    "type": "string",
                    "description": "Der Identity.md-Inhalt für einen neuen Agenten (nur für create_agent)"
                },
                "soul": {
                    "type": "string",
                    "description": "Der Soul.md-Inhalt für einen neuen Agenten (nur für create_agent)"
                }
            },
            "required": ["action"]
        }
        """).RootElement.Clone();

    public async Task<ToolResult> ExecuteAsync(
        JsonElement input,
        AgentToolContext context,
        CancellationToken ct)
    {
        var action = input.GetProperty("action").GetString()
            ?? throw new ArgumentException("'action' is required");

        try
        {
            var agentsDir = ResolveAgentsDirectory(context);
            if (agentsDir is null)
                return ToolResult.Fail("Konnte das Agents-Verzeichnis nicht ermitteln. SharedWorkspacePath ist nicht konfiguriert.");

            return action switch
            {
                "list_agents" => HandleListAgents(agentsDir, context),
                "read_identity" => await HandleReadFileAsync(input, agentsDir, "Identity.md", context, ct),
                "read_soul" => await HandleReadFileAsync(input, agentsDir, "Soul.md", context, ct),
                "update_identity" => await HandleUpdateFileAsync(input, agentsDir, "Identity.md", context, ct),
                "update_soul" => await HandleUpdateFileAsync(input, agentsDir, "Soul.md", context, ct),
                "create_agent" => await HandleCreateAgentAsync(input, agentsDir, context, ct),
                _ => ToolResult.Fail($"Unbekannte Aktion: {action}")
            };
        }
        catch (Exception ex)
        {
            context.Logger.LogError(ex, "Fehler bei AgentEditor Aktion {Action}", action);
            return ToolResult.Fail($"Fehler: {ex.Message}");
        }
    }

    /// <summary>
    /// Ermittelt das Agents-Verzeichnis aus dem SharedWorkspacePath.
    /// SharedWorkspace liegt unter .../Agents/SharedWorkspace → Parent = Agents-Verzeichnis.
    /// </summary>
    private static string? ResolveAgentsDirectory(AgentToolContext context)
    {
        if (string.IsNullOrWhiteSpace(context.SharedWorkspacePath))
            return null;

        // SharedWorkspace = .../Agents/SharedWorkspace → Parent = .../Agents/
        var agentsDir = Path.GetDirectoryName(context.SharedWorkspacePath);
        return agentsDir != null && Directory.Exists(agentsDir) ? agentsDir : null;
    }

    /// <summary>
    /// Findet das Agent-Verzeichnis anhand der agentId.
    /// Sucht nach Agent-* Ordnern und gleicht mit der AgentSettings.json ab.
    /// </summary>
    private static string? FindAgentDirectory(string agentsDir, string agentId)
    {
        foreach (var dir in Directory.GetDirectories(agentsDir, "Agent-*"))
        {
            var settingsPath = Path.Combine(dir, "AgentSettings.json");
            if (!File.Exists(settingsPath)) continue;

            try
            {
                var json = File.ReadAllText(settingsPath);
                using var doc = JsonDocument.Parse(json);
                var id = doc.RootElement.TryGetProperty("agentId", out var idElem)
                    ? idElem.GetString() : null;

                if (string.Equals(id, agentId, StringComparison.OrdinalIgnoreCase))
                    return dir;
            }
            catch
            {
                // Fehlerhafte JSON-Datei ignorieren
            }
        }

        // Fallback: Ordnername-basiert (Agent-Senior_Developer → senior_developer)
        foreach (var dir in Directory.GetDirectories(agentsDir, "Agent-*"))
        {
            var folderName = Path.GetFileName(dir).Replace("Agent-", "");
            if (string.Equals(folderName, agentId, StringComparison.OrdinalIgnoreCase))
                return dir;
        }

        return null;
    }

    // ═══════════════════════════════════════════════════
    //  AKTIONEN
    // ═══════════════════════════════════════════════════

    private ToolResult HandleListAgents(string agentsDir, AgentToolContext context)
    {
        var agents = new List<object>();

        foreach (var dir in Directory.GetDirectories(agentsDir, "Agent-*").OrderBy(d => d))
        {
            var folderName = Path.GetFileName(dir);
            var settingsPath = Path.Combine(dir, "AgentSettings.json");

            string agentId = folderName.Replace("Agent-", "").ToLowerInvariant();
            string displayName = folderName.Replace("Agent-", "").Replace("_", " ");
            string description = "";
            var toolNames = new List<string>();

            if (File.Exists(settingsPath))
            {
                try
                {
                    using var doc = JsonDocument.Parse(File.ReadAllText(settingsPath));
                    var root = doc.RootElement;

                    if (root.TryGetProperty("agentId", out var idEl))
                        agentId = idEl.GetString() ?? agentId;
                    if (root.TryGetProperty("displayName", out var nameEl))
                        displayName = nameEl.GetString() ?? displayName;
                    if (root.TryGetProperty("tools", out var toolsEl) && toolsEl.ValueKind == JsonValueKind.Object)
                        toolNames = toolsEl.EnumerateObject().Select(p => p.Name).ToList();
                }
                catch { /* fehlerhafte JSON ignorieren */ }
            }

            // Description aus AgentList.json holen
            var agentListPath = Path.Combine(agentsDir, "AgentList.json");
            if (File.Exists(agentListPath))
            {
                try
                {
                    using var listDoc = JsonDocument.Parse(File.ReadAllText(agentListPath));
                    if (listDoc.RootElement.TryGetProperty("agents", out var agentsArr))
                    {
                        foreach (var entry in agentsArr.EnumerateArray())
                        {
                            if (entry.TryGetProperty("folderName", out var fn) &&
                                fn.GetString() == folderName &&
                                entry.TryGetProperty("description", out var desc))
                            {
                                description = desc.GetString() ?? "";
                                break;
                            }
                        }
                    }
                }
                catch { /* ignorieren */ }
            }

            var hasIdentity = File.Exists(Path.Combine(dir, "Identity.md"));
            var hasSoul = File.Exists(Path.Combine(dir, "Soul.md"));
            var isSelf = string.Equals(agentId, context.AgentId, StringComparison.OrdinalIgnoreCase);

            agents.Add(new
            {
                AgentId = agentId,
                DisplayName = displayName,
                Description = description,
                FolderName = folderName,
                HasIdentity = hasIdentity,
                HasSoul = hasSoul,
                Tools = toolNames,
                IsSelf = isSelf
            });
        }

        return ToolResult.Ok(JsonSerializer.Serialize(agents, new JsonSerializerOptions { WriteIndented = true }));
    }

    private async Task<ToolResult> HandleReadFileAsync(
        JsonElement input, string agentsDir, string fileName, AgentToolContext context, CancellationToken ct)
    {
        var agentId = input.TryGetProperty("agentId", out var ai) ? ai.GetString() : null;
        if (string.IsNullOrWhiteSpace(agentId))
            return ToolResult.Fail("'agentId' ist erforderlich. Nutze zuerst list_agents für eine Übersicht.");

        var agentDir = FindAgentDirectory(agentsDir, agentId);
        if (agentDir is null)
            return ToolResult.Fail($"Agent '{agentId}' nicht gefunden. Nutze list_agents für eine Übersicht der verfügbaren Agenten.");

        var filePath = Path.Combine(agentDir, fileName);
        if (!File.Exists(filePath))
            return ToolResult.Fail($"{fileName} existiert nicht für Agent '{agentId}'.");

        var content = await File.ReadAllTextAsync(filePath, Encoding.UTF8, ct);
        var label = fileName.Replace(".md", "");
        return ToolResult.Ok($"[{label} von '{agentId}']\n\n{content}");
    }

    private async Task<ToolResult> HandleUpdateFileAsync(
        JsonElement input, string agentsDir, string fileName, AgentToolContext context, CancellationToken ct)
    {
        var agentId = input.TryGetProperty("agentId", out var ai) ? ai.GetString() : null;
        var content = input.TryGetProperty("content", out var c) ? c.GetString() : null;

        if (string.IsNullOrWhiteSpace(agentId))
            return ToolResult.Fail("'agentId' ist erforderlich.");

        if (string.IsNullOrWhiteSpace(content))
            return ToolResult.Fail("'content' ist erforderlich. Lies zuerst die bestehende Datei mit read_identity/read_soul.");

        var agentDir = FindAgentDirectory(agentsDir, agentId);
        if (agentDir is null)
            return ToolResult.Fail($"Agent '{agentId}' nicht gefunden.");

        var filePath = Path.Combine(agentDir, fileName);

        // Backup erstellen falls Datei existiert
        if (File.Exists(filePath))
        {
            var backupPath = filePath + $".bak_{DateTime.Now:yyyyMMdd_HHmmss}";
            File.Copy(filePath, backupPath, overwrite: true);
            context.Logger.LogInformation(
                "Backup erstellt: {BackupPath}", Path.GetFileName(backupPath));
        }

        await File.WriteAllTextAsync(filePath, content, new UTF8Encoding(false), ct);

        var label = fileName.Replace(".md", "");
        return ToolResult.Ok(
            $"{label} von Agent '{agentId}' erfolgreich aktualisiert.\n" +
            $"Hinweis: Die Änderungen werden beim nächsten Neustart des Agenten oder der Instanz wirksam.");
    }

    private async Task<ToolResult> HandleCreateAgentAsync(
        JsonElement input, string agentsDir, AgentToolContext context, CancellationToken ct)
    {
        var agentName = input.TryGetProperty("agentName", out var an) ? an.GetString() : null;
        var description = input.TryGetProperty("agentDescription", out var ad) ? ad.GetString() ?? "" : "";
        var identity = input.TryGetProperty("identity", out var id) ? id.GetString() : null;
        var soul = input.TryGetProperty("soul", out var so) ? so.GetString() : null;

        if (string.IsNullOrWhiteSpace(agentName))
            return ToolResult.Fail("'agentName' ist erforderlich (z.B. 'News Scanner').");

        // Ordnername sanitieren
        var sanitized = SanitizeName(agentName);
        var folderName = $"Agent-{sanitized}";
        var agentDir = Path.Combine(agentsDir, folderName);

        if (Directory.Exists(agentDir))
            return ToolResult.Fail($"Agent '{agentName}' existiert bereits (Ordner: {folderName}).");

        // Verzeichnisse anlegen
        Directory.CreateDirectory(agentDir);
        Directory.CreateDirectory(Path.Combine(agentDir, "Logs"));
        Directory.CreateDirectory(Path.Combine(agentDir, "Workspace"));

        var agentId = sanitized.ToLowerInvariant();

        // AgentSettings.json — minimale Grundkonfiguration
        var settings = new
        {
            agentId,
            displayName = agentName,
            model = "anthropic/claude-sonnet-4-5",
            systemPrompt = "",
            tools = new Dictionary<string, object>(),
            scheduler = (object?)null,
            toolJobs = Array.Empty<object>(),
            promptCaching = "auto",
            maxToolResultChars = 16000,
            loopGuard = new
            {
                maxSteps = 20,
                maxCumulativeTokens = 500000,
                timeoutSeconds = 600,
                maxContextTokens = 100000,
                compactionThreshold = 0.8,
                summaryModel = "google/gemini-2.5-flash"
            }
        };
        await File.WriteAllTextAsync(
            Path.Combine(agentDir, "AgentSettings.json"),
            JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true }),
            new UTF8Encoding(false), ct);

        // Identity.md
        var identityContent = identity ?? $"""
            # Identity: {agentName}

            Du bist **{agentName}**, ein spezialisierter KI-Agent im ClawdDotNet-System.

            ## Rolle
            {(string.IsNullOrWhiteSpace(description) ? "[Beschreibe hier die Rolle und Verantwortlichkeiten]" : description)}

            ## Expertise
            [Beschreibe hier die Fachgebiete und Fähigkeiten]

            ## Kontext
            [Beschreibe hier den Arbeitskontext und die Teamzugehörigkeit]
            """;
        await File.WriteAllTextAsync(
            Path.Combine(agentDir, "Identity.md"), identityContent, new UTF8Encoding(false), ct);

        // Soul.md
        var soulContent = soul ?? $"""
            # Soul: {agentName}

            ## Persönlichkeit
            - Gründlich und zuverlässig
            - Klar und präzise in der Kommunikation
            - Proaktiv bei der Problemerkennung

            ## Arbeitsweise
            - Analysiere Aufgaben sorgfältig bevor du handelst
            - Dokumentiere deine Entscheidungen und Ergebnisse
            - Nutze die dir zugewiesenen Tools effizient

            ## Werte
            - Genauigkeit vor Geschwindigkeit
            - Transparenz in der Entscheidungsfindung
            - Sicherheit und Datenschutz haben Priorität
            """;
        await File.WriteAllTextAsync(
            Path.Combine(agentDir, "Soul.md"), soulContent, new UTF8Encoding(false), ct);

        // Leere Chat-Dateien
        await File.WriteAllTextAsync(Path.Combine(agentDir, "ChatHistory.json"), "[]", ct);
        await File.WriteAllTextAsync(Path.Combine(agentDir, "ChatContext.json"), "[]", ct);

        // AgentList.json aktualisieren
        await UpdateAgentListAsync(agentsDir, agentName, description, folderName, ct);

        return ToolResult.Ok(
            $"Agent '{agentName}' erfolgreich erstellt!\n\n" +
            $"  Ordner: {folderName}\n" +
            $"  AgentId: {agentId}\n" +
            $"  Identity: {(identity != null ? "Benutzerdefiniert" : "Standard-Template")}\n" +
            $"  Soul: {(soul != null ? "Benutzerdefiniert" : "Standard-Template")}\n\n" +
            $"Hinweis: Dem neuen Agenten wurden noch KEINE Tools zugewiesen. " +
            $"Dies muss über die AgentSettings.json oder die UI erfolgen. " +
            $"Die Instanz muss neu gestartet werden, damit der Agent verfügbar wird.");
    }

    // ═══════════════════════════════════════════════════
    //  HILFSMETHODEN
    // ═══════════════════════════════════════════════════

    private static string SanitizeName(string name)
    {
        var sb = new StringBuilder();
        foreach (var c in name)
        {
            if (char.IsLetterOrDigit(c))
                sb.Append(c);
            else if (c is ' ' or '-' or '_')
                sb.Append('_');
        }
        return sb.ToString();
    }

    private static async Task UpdateAgentListAsync(
        string agentsDir, string agentName, string description, string folderName, CancellationToken ct)
    {
        var agentListPath = Path.Combine(agentsDir, "AgentList.json");

        List<Dictionary<string, string>> agents = new();

        if (File.Exists(agentListPath))
        {
            try
            {
                var json = await File.ReadAllTextAsync(agentListPath, ct);
                using var doc = JsonDocument.Parse(json);

                if (doc.RootElement.TryGetProperty("agents", out var agentsArr))
                {
                    foreach (var entry in agentsArr.EnumerateArray())
                    {
                        var item = new Dictionary<string, string>();
                        foreach (var prop in entry.EnumerateObject())
                            item[prop.Name] = prop.Value.GetString() ?? "";
                        agents.Add(item);
                    }
                }
            }
            catch { /* fehlerhafte JSON → neu aufbauen */ }
        }

        // Duplikat-Check
        if (agents.All(a => !a.TryGetValue("folderName", out var fn) || fn != folderName))
        {
            agents.Add(new Dictionary<string, string>
            {
                ["name"] = agentName,
                ["description"] = description,
                ["folderName"] = folderName
            });
        }

        var result = new { agents };
        await File.WriteAllTextAsync(
            agentListPath,
            JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }),
            new UTF8Encoding(false), ct);
    }
}
