using System.Collections.ObjectModel;
using ClawdDotNet.App;
using ClawdDotNet.Core.Config;
using ClawdDotNet.Core.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;

namespace ClawdDotNet.Desktop.ViewModels;

public sealed partial class ToolItemViewModel : ObservableObject
{
    public string Name { get; }

    /// <summary>
    /// Kurzbeschreibung aus der Werkzeug-Registrierung. Leer, wenn das Werkzeug in dieser
    /// Instanz gar nicht geladen ist — die Liste der bekannten Namen ist länger als die
    /// der tatsächlich vorhandenen Werkzeuge.
    /// </summary>
    public string Description { get; }

    [ObservableProperty]
    private bool _isEnabled;

    public ToolItemViewModel(string name, bool isEnabled, string description = "")
    {
        Name = name;
        Description = description;
        _isEnabled = isEnabled;
    }
}

public sealed partial class AgentItemViewModel : ObservableObject
{
    public AgentConfig Config { get; }

    public string AgentId => Config.AgentId;

    [ObservableProperty]
    private string _displayName;

    [ObservableProperty]
    private string _model;

    [ObservableProperty]
    private string _systemPrompt;

    [ObservableProperty]
    private string _identity;

    [ObservableProperty]
    private string _soul;

    [ObservableProperty]
    private string _description;

    [ObservableProperty]
    private string _promptCaching;

    [ObservableProperty]
    private int _maxToolResultChars;

    [ObservableProperty]
    private int _maxSteps;

    [ObservableProperty]
    private int _maxContextTokens;

    [ObservableProperty]
    private int _timeoutSeconds;

    [ObservableProperty]
    private decimal _dailyCostUsd;

    [ObservableProperty]
    private long _dailyTokens;

    public ObservableCollection<ToolItemViewModel> AvailableTools { get; } = [];

    public static readonly List<string> KnownTools =
    [
        "FileRW", "Telegram", "Mail", "Database", "FTP",
        "DirectAPI", "WebFetch", "WebMonitor", "AgentComm",
        "SocialMediaManager", "AgentSpawn", "AgentEditor", "Memory", "Taskboard"
    ];

    public AgentItemViewModel(AgentConfig config, IReadOnlyDictionary<string, string>? descriptions = null)
    {
        Config = config;
        _displayName = config.DisplayName;
        _model = config.Model;
        _systemPrompt = config.SystemPrompt;
        _identity = config.Identity;
        _soul = config.Soul;
        _description = config.Description;
        _promptCaching = config.PromptCaching;
        _maxToolResultChars = config.MaxToolResultChars;
        _maxSteps = config.LoopGuard.MaxSteps;
        _maxContextTokens = config.LoopGuard.MaxContextTokens;
        _timeoutSeconds = config.LoopGuard.TimeoutSeconds;
        _dailyCostUsd = config.Budget.DailyCostUsd;
        _dailyTokens = config.Budget.DailyTokens;

        foreach (var tool in KnownTools)
        {
            var isEnabled = config.Tools.ContainsKey(tool);
            var description = descriptions?.GetValueOrDefault(tool) ?? "";

            var item = new ToolItemViewModel(tool, isEnabled, description);
            item.PropertyChanged += (_, e) =>
            {
                if (e.PropertyName == nameof(ToolItemViewModel.IsEnabled))
                {
                    if (item.IsEnabled && !Config.Tools.ContainsKey(tool))
                        Config.Tools[tool] = new Dictionary<string, object?>();
                    else if (!item.IsEnabled)
                        Config.Tools.Remove(tool);
                }
            };
            AvailableTools.Add(item);
        }
    }

    public void ApplyChanges()
    {
        Config.DisplayName = DisplayName;
        Config.Model = Model;
        Config.SystemPrompt = SystemPrompt;
        Config.Identity = Identity;
        Config.Soul = Soul;
        Config.Description = Description;
        Config.PromptCaching = PromptCaching;
        Config.MaxToolResultChars = MaxToolResultChars;
        Config.LoopGuard.MaxSteps = MaxSteps;
        Config.LoopGuard.MaxContextTokens = MaxContextTokens;
        Config.LoopGuard.TimeoutSeconds = TimeoutSeconds;
        Config.Budget.DailyCostUsd = DailyCostUsd;
        Config.Budget.DailyTokens = DailyTokens;
    }
}

/// <summary>Welches Feld der Texteditor gerade bearbeitet.</summary>
public enum AgentTextField
{
    Identity,
    Soul
}

/// <summary>
/// Ansichtsmodell für die Agenten-Verwaltungsseite.
///
/// <para>Der Entwurf ordnet die Seite wieder wie die WinForms-Vorlage in vier Felder an:
/// oben Agentenliste und Agenteneinstellungen, unten Werkzeugliste und
/// Werkzeugeinstellungen. Für dieses Modell heißt das vor allem: Die Werkzeugeinstellungen
/// sind kein eigenes Fenster mehr, sondern hängen als <see cref="ToolSettings"/> an der
/// Auswahl in der Werkzeugliste.</para>
/// </summary>
public sealed partial class AgentsPageViewModel : PageViewModel
{
    private readonly AppHost? _host;
    private readonly ILogger? _logger;

    public ObservableCollection<AgentItemViewModel> Agents { get; } = [];

    [ObservableProperty]
    private AgentItemViewModel? _selectedAgent;

    /// <summary>
    /// Das ausgewählte Werkzeug des ausgewählten Agenten. Steuert, welche Felder unten
    /// rechts stehen.
    /// </summary>
    [ObservableProperty]
    private ToolItemViewModel? _selectedTool;

    /// <summary>
    /// Die Felder zum ausgewählten Werkzeug. Wird bei jedem Wechsel neu gebaut — das
    /// Ansichtsmodell trägt die Werte von einem guten Dutzend Werkzeuge, und eines davon
    /// mit den Werten eines anderen weiterzuverwenden ginge irgendwann schief.
    /// </summary>
    [ObservableProperty]
    private ToolSettingsViewModel? _toolSettings;

    [ObservableProperty]
    private string _statusText = "Bereit";

    [ObservableProperty]
    private bool _isRunning;

    public List<string> KnownModels { get; } =
    [
        "anthropic/claude-3-5-sonnet",
        "anthropic/claude-3-opus",
        "openai/gpt-4o",
        "openai/gpt-4o-mini",
        "google/gemini-2.5-flash",
        "google/gemini-2.5-pro",
        "deepseek/deepseek-r1"
    ];

    public List<string> CachingOptions { get; } = ["auto", "on", "off"];

    public event Func<AddAgentViewModel, Task<AddAgentResultData?>>? RequestAddAgentDialog;

    /// <summary>
    /// Öffnet den Editor für Identity bzw. Soul. Gibt der Aufrufer einen Text zurück,
    /// wurde gespeichert; <c>null</c> heißt Abbruch.
    /// </summary>
    public event Func<string, string, Task<string?>>? RequestTextEditorDialog;

    public AgentsPageViewModel() : this(null) { }

    public AgentsPageViewModel(AppHost? host) : base("Agenten")
    {
        _host = host;

        if (host is not null)
        {
            _logger = host.LoggerFactory.CreateLogger("ClawdDotNet.Desktop.Agents");
            ReloadAgents();
        }
        else
        {
            StatusText = "Entwurfsmodus";
        }
    }

    /// <summary>
    /// Beschreibungen der tatsächlich geladenen Werkzeuge. Die Namensliste in
    /// <see cref="AgentItemViewModel.KnownTools"/> ist fest verdrahtet und kennt auch
    /// Werkzeuge, die in dieser Instanz nicht registriert sind — für die bleibt die
    /// Beschreibung eben leer.
    /// </summary>
    private IReadOnlyDictionary<string, string> ToolDescriptions()
        => _host?.Tools.GetAll()
               .GroupBy(t => t.Name, StringComparer.OrdinalIgnoreCase)
               .ToDictionary(g => g.Key, g => g.First().Description, StringComparer.OrdinalIgnoreCase)
           ?? new Dictionary<string, string>();

    [RelayCommand]
    private void ReloadAgents()
    {
        Agents.Clear();
        if (_host is null) return;

        var descriptions = ToolDescriptions();

        foreach (var agent in _host.Instance.Agents)
            Agents.Add(new AgentItemViewModel(agent, descriptions));

        SelectedAgent = Agents.FirstOrDefault();
    }

    partial void OnSelectedAgentChanged(AgentItemViewModel? value)
    {
        // Die Werkzeugliste unten gehört zum gewählten Agenten — bei einem Wechsel darf
        // dort nicht die Auswahl des vorigen stehen bleiben.
        SelectedTool = value?.AvailableTools.FirstOrDefault();
    }

    partial void OnSelectedToolChanged(ToolItemViewModel? value)
    {
        if (SelectedAgent is null || value is null)
        {
            ToolSettings = null;
            return;
        }

        // Auch für ein noch nicht zugelassenes Werkzeug sollen die Felder sichtbar sein —
        // sonst müsste man erst ankreuzen, um zu sehen, was einzustellen wäre.
        if (!SelectedAgent.Config.Tools.TryGetValue(value.Name, out var config) || config is null)
            config = new Dictionary<string, object?>();

        ToolSettings = new ToolSettingsViewModel(value.Name, config);
    }

    [RelayCommand]
    private async Task AddAgentAsync()
    {
        if (_host is null || RequestAddAgentDialog is null) return;

        var vm = new AddAgentViewModel();
        var result = await RequestAddAgentDialog(vm);
        if (result is null) return;

        if (_host.Instance.Agents.Any(a => a.AgentId.Equals(result.AgentId, StringComparison.OrdinalIgnoreCase)))
        {
            StatusText = $"Ein Agent mit der ID '{result.AgentId}' existiert bereits.";
            return;
        }

        var newConfig = new AgentConfig
        {
            AgentId = result.AgentId,
            DisplayName = result.DisplayName,
            Model = result.Model,
            Description = result.Description
        };

        _host.Instance.Agents.Add(newConfig);
        SaveAgents();

        var item = new AgentItemViewModel(newConfig, ToolDescriptions());
        Agents.Add(item);
        SelectedAgent = item;

        StatusText = $"Agent '{result.DisplayName}' angelegt.";
    }

    [RelayCommand]
    private void RemoveAgent()
    {
        if (_host is null || SelectedAgent is null) return;

        var agentId = SelectedAgent.AgentId;
        _host.Instance.Agents.RemoveAll(a => a.AgentId == agentId);

        SaveAgents();
        Agents.Remove(SelectedAgent);
        SelectedAgent = Agents.FirstOrDefault();

        StatusText = $"Agent '{agentId}' entfernt.";
    }

    /// <summary>
    /// Weckt den gewählten Agenten von Hand — im Entwurf die Schaltfläche „Jetzt ausf.".
    /// Nimmt dieselbe Nachricht wie der geplante Lauf, damit der Handlauf zeigt, was der
    /// geplante täte.
    /// </summary>
    [RelayCommand]
    private async Task RunAgentNowAsync()
    {
        if (_host is null || SelectedAgent is null) return;

        if (_host.Engine is null)
        {
            StatusText = "Agent-Engine ist nicht aktiv (kein API-Key).";
            return;
        }

        var config = SelectedAgent.Config;
        var message = config.Scheduler?.TaskMessage ?? "Führe deine zugewiesenen Aufgaben aus.";

        IsRunning = true;
        StatusText = $"Führe '{config.DisplayName}' aus…";

        try
        {
            var result = await _host.Engine.RunAsync(
                config, message, _host.Instance.InstanceId, CancellationToken.None);

            StatusText = $"'{config.DisplayName}' abgeschlossen: {result.Status} "
                         + $"({result.StepCount} Schritte, {result.TokensUsed:N0} Tokens).";
        }
        catch (Exception ex)
        {
            StatusText = $"Lauf fehlgeschlagen: {ex.Message}";
            _logger?.LogError(ex, "Handlauf des Agenten fehlgeschlagen");
        }
        finally
        {
            IsRunning = false;
        }
    }

    [RelayCommand]
    private Task EditIdentityAsync() => EditTextFieldAsync(AgentTextField.Identity);

    [RelayCommand]
    private Task EditSoulAsync() => EditTextFieldAsync(AgentTextField.Soul);

    /// <summary>
    /// Identity und Soul sind lange Texte. Der Entwurf gibt ihnen ein eigenes kleines
    /// Fenster statt eines Feldes in der Spalte — in einem 60 Pixel hohen Kasten lässt
    /// sich eine Rollenbeschreibung nicht schreiben.
    /// </summary>
    private async Task EditTextFieldAsync(AgentTextField field)
    {
        if (SelectedAgent is null || RequestTextEditorDialog is null) return;

        var isIdentity = field == AgentTextField.Identity;
        var title = isIdentity
            ? $"Identity bearbeiten — {SelectedAgent.DisplayName}"
            : $"Soul bearbeiten — {SelectedAgent.DisplayName}";

        var current = isIdentity ? SelectedAgent.Identity : SelectedAgent.Soul;

        var edited = await RequestTextEditorDialog(title, current);
        if (edited is null) return;

        if (isIdentity) SelectedAgent.Identity = edited;
        else SelectedAgent.Soul = edited;

        SaveAgents();
    }

    /// <summary>Öffnet den Arbeitsordner des gewählten Agenten.</summary>
    [RelayCommand]
    private void OpenAgentFolder()
    {
        if (_host is null || SelectedAgent is null) return;

        var path = _host.Directories.GetAgentPath(_host.InstancePath, SelectedAgent.DisplayName);

        if (!Directory.Exists(path))
        {
            StatusText = $"Ordner nicht gefunden: {path}";
            return;
        }

        SystemShell.OpenFolder(path);
    }

    /// <summary>
    /// Übernimmt die Felder des gewählten Werkzeugs in die Agentenkonfiguration.
    ///
    /// <para>Das Ankreuzfeld in der Liste und diese Schaltfläche sind zwei verschiedene
    /// Dinge: Ersteres lässt das Werkzeug zu, letztere schreibt seine Einstellungen. Wer
    /// nur ankreuzt, bekommt ein Werkzeug mit Vorgabewerten — das ist gewollt.</para>
    /// </summary>
    [RelayCommand]
    private void SaveToolSettings()
    {
        if (SelectedAgent is null || SelectedTool is null || ToolSettings is null) return;

        ToolSettings.ConfirmCommand.Execute(null);

        if (ToolSettings.Result is not { } config) return;

        SelectedAgent.Config.Tools[SelectedTool.Name] = config;

        // Einstellungen zu schreiben heißt, das Werkzeug zu wollen.
        SelectedTool.IsEnabled = true;

        SaveAgents();
        StatusText = $"Einstellungen für '{SelectedTool.Name}' gespeichert.";
    }

    [RelayCommand]
    private void SaveAgents()
    {
        if (_host is null) return;

        foreach (var agentVm in Agents)
            agentVm.ApplyChanges();

        _host.Directories.SaveInstanceConfig(_host.InstancePath, _host.Instance);
        _logger?.LogInformation("Agenten-Konfiguration gespeichert.");
        StatusText = "Agenten-Konfiguration gespeichert.";
    }

    // ─── Teilerstellungen ───
    //
    // Die drei Teiler der Seite sollen den Neustart ueberleben. Gespeichert wird ein
    // Verhaeltnis, kein Pixelwert: Sonst sitzt der Teiler nach einem Wechsel auf einen
    // anderen Bildschirm an einer voellig anderen Stelle.

    public double SplitVertical => Clamp(_host?.Settings.AppSettings.AgentsSplitVertical ?? 0.55);
    public double SplitTopList => Clamp(_host?.Settings.AppSettings.AgentsSplitTopList ?? 0.44);
    public double SplitBottomList => Clamp(_host?.Settings.AppSettings.AgentsSplitBottomList ?? 0.44);

    /// <summary>
    /// Hält den Teiler in einem Bereich, in dem beide Seiten noch bedienbar sind. Ein
    /// gespeicherter Wert von 0.02 hätte eine Spalte auf einen Strich zusammengezogen und
    /// wäre ohne Zurücksetzen nicht mehr aufzuziehen gewesen.
    /// </summary>
    private static double Clamp(double ratio)
        => double.IsFinite(ratio) ? Math.Clamp(ratio, 0.2, 0.8) : 0.5;

    public void SaveSplits(double vertical, double topList, double bottomList)
    {
        if (_host is null) return;

        try
        {
            var settings = _host.Settings.AppSettings;
            settings.AgentsSplitVertical = Clamp(vertical);
            settings.AgentsSplitTopList = Clamp(topList);
            settings.AgentsSplitBottomList = Clamp(bottomList);
            _host.Settings.Save();
        }
        catch (Exception)
        {
            // Eine Teilerstellung ist es nicht wert, die Bedienung zu unterbrechen.
        }
    }
}
