using System.Collections.ObjectModel;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Predictalytics.Hosting;
using Serilog;
using Serilog.Events;

namespace Predictalytics.Shell.ViewModels;

/// <summary>
/// Bindungsziel des Hauptfensters. Haelt den <see cref="PredictalyticsHost"/> und
/// uebersetzt dessen Zustand in Anzeigewerte.
/// </summary>
public sealed partial class MainWindowViewModel : ObservableObject
{
    /// <summary>Obergrenze der Terminalzeilen. Aeltere werden vorne verworfen.</summary>
    private const int MaxLogLines = 500;

    private readonly PredictalyticsHost _host;
    private CancellationTokenSource? _workerCts;
    private DcHeartbeatService? _heartbeat;
    private double? _lastDbSizeMb;

    /// <summary>Wird gesetzt, sobald das Fenster steht — fuer Dialoge und Fehlermeldungen.</summary>
    public Func<string, string, Task>? ShowInfo { get; set; }
    public Func<string, string, Task>? ShowError { get; set; }
    public Func<string, string, Task<bool>>? ShowConfirm { get; set; }

    public PredictalyticsOptions Options { get; }

    public ObservableCollection<LogLine> LogLines { get; } = [];

    [ObservableProperty] private string _statusText = "";
    [ObservableProperty] private string _dbSizeText = "DB Size: —";
    [ObservableProperty] private string _buildVersionText = "";
    [ObservableProperty] private string _serverButtonText = "▶ Start Server";
    [ObservableProperty] private string _webserverButtonText = "▶ Start Webserver";
    [ObservableProperty] private bool _isBusy;

    /// <summary>
    /// Die Lizenz, auf der dieser Lauf beruht — wird nach der Lizenzschranke gesetzt und
    /// vom Menuepunkt „Lizenzstatus" gelesen. Das ViewModel entsteht bewusst vorher, damit
    /// das Terminal schon waehrend der Aktivierung mitschreibt.
    /// </summary>
    public LicenseSession? License { get; set; }

    public MainWindowViewModel(PredictalyticsOptions options)
    {
        Options = options;
        _host = new PredictalyticsHost(Options);
        _host.StateChanged += () => Dispatcher.UIThread.Post(UpdateStatus);

        try
        {
            var buildDate = new FileInfo(GetType().Assembly.Location).LastWriteTime;
            BuildVersionText = $"v{DcConfig.AppVersion} — Build: {buildDate:yyyy-MM-dd HH:mm:ss}";
        }
        catch
        {
            BuildVersionText = $"v{DcConfig.AppVersion}";
        }

        UpdateStatus();
    }

    /// <summary>Wird nach dem Serilog-Aufbau aufgerufen.</summary>
    public void Start()
    {
        Log.Information("Shell initialisiert. Bereit.");
        Log.Information("'Start Server' beginnt Polling und Discovery.");
        Log.Information("'Start Webserver' startet die WebUI auf {Url}", Options.WebserverUrl);
        Log.Information("Einstellungen: {Path}", PredictalyticsOptions.SettingsFilePath);

        _ = RefreshDbSizeAsync();
        var dbSizeTimer = new DispatcherTimer { Interval = TimeSpan.FromHours(6) };
        dbSizeTimer.Tick += async (_, _) => await RefreshDbSizeAsync();
        dbSizeTimer.Start();

        RestartHeartbeat();

        if (Options.DcUpdateCheckEnabled)
        {
            _ = CheckForUpdatesAsync(silent: true);
        }
    }

    /// <summary>Terminal-Sink: wird von Serilog aus beliebigen Threads gerufen.</summary>
    public void AppendLog(string message, LogEventLevel level)
    {
        var text = message.TrimEnd('\r', '\n');
        Dispatcher.UIThread.Post(() =>
        {
            LogLines.Add(new LogLine(text, level));
            while (LogLines.Count > MaxLogLines) LogLines.RemoveAt(0);
        });
    }

    private void UpdateStatus()
    {
        var workers = _host.WorkersRunning ? "[RUNNING] Workers" : "[STOPPED] Workers";
        var web = _host.WebServerRunning ? $"[RUNNING] Webserver :{Options.WebserverPort}" : "[STOPPED] Webserver";
        StatusText = $"{workers}  |  {web}";
        ServerButtonText = _host.WorkersRunning ? "⏹ Stop Server" : "▶ Start Server";
        WebserverButtonText = _host.WebServerRunning ? "⏹ Stop Webserver" : "▶ Start Webserver";
    }

    // ─── Deployment Center: Heartbeat ───

    private void RestartHeartbeat()
    {
        _heartbeat?.Dispose();
        _heartbeat = null;

        if (!Options.DcHeartbeatEnabled) return;

        if (string.IsNullOrWhiteSpace(Options.DcToken))
        {
            Log.Information("🐕 Heartbeat ist aktiviert, aber es fehlt das Deployment-Center-Token — bitte in den Einstellungen eintragen.");
            return;
        }

        _heartbeat = new DcHeartbeatService(
            Options.DcToken,
            Options.DcSource,
            Options.DcInstance,
            Options.DcHeartbeatIntervalSeconds,
            CollectHeartbeatSnapshotAsync);
        _heartbeat.Start();
    }

    /// <summary>
    /// Assembles what this app knows about its own health. A heartbeat alone only proves that
    /// a timer runs — the DB check is what shows whether the app can actually do its work.
    /// </summary>
    private async Task<DcHeartbeatSnapshot> CollectHeartbeatSnapshotAsync(CancellationToken ct)
    {
        var snapshot = new DcHeartbeatSnapshot
        {
            Message = _host.WorkersRunning ? "Worker laufen" : "Worker gestoppt"
        };

        // Deliberately no check for "workers stopped": that is a legitimate state chosen by
        // the operator and would otherwise keep the monitor permanently on warning.
        snapshot.Metrics["workers_running"] = _host.WorkersRunning ? 1 : 0;
        snapshot.Metrics["webserver_running"] = _host.WebServerRunning ? 1 : 0;
        snapshot.Metrics["memory_mb"] = Math.Round(GC.GetTotalMemory(forceFullCollection: false) / 1024d / 1024d, 1);
        if (_lastDbSizeMb is { } dbSize) snapshot.Metrics["db_size_mb"] = Math.Round(dbSize, 2);

        var db = await ProbeDatabaseAsync(ct);
        if (db is not null) snapshot.Checks["db"] = db;

        return snapshot;
    }

    /// <summary>SELECT 1 against the configured MySQL, capped so it cannot stall the heartbeat.</summary>
    private async Task<DcCheck?> ProbeDatabaseAsync(CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(Options.DbName)) return null;

        var started = System.Diagnostics.Stopwatch.StartNew();
        try
        {
            using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
            timeout.CancelAfter(TimeSpan.FromSeconds(5));

            await using var conn = new MySqlConnector.MySqlConnection(Options.ConnectionString);
            await conn.OpenAsync(timeout.Token);
            await using var cmd = conn.CreateCommand();
            cmd.CommandText = "SELECT 1";
            await cmd.ExecuteScalarAsync(timeout.Token);

            return new DcCheck(true, $"{started.ElapsedMilliseconds} ms", started.ElapsedMilliseconds);
        }
        catch (Exception ex)
        {
            return new DcCheck(false, ex.Message);
        }
    }

    // ─── Deployment Center: Updates und Lizenz ───

    /// <summary>
    /// Asks the UpdateService for a newer release. Silent at startup (log + status bar);
    /// only a critical release interrupts the user.
    /// </summary>
    [RelayCommand]
    private Task CheckForUpdatesInteractiveAsync() => CheckForUpdatesAsync(silent: false);

    private async Task CheckForUpdatesAsync(bool silent)
    {
        try
        {
            var result = await DcUpdateService.CheckAsync(Options.DcUpdateChannel);

            if (result.Error is not null)
            {
                Log.Warning("Update-Prüfung fehlgeschlagen: {Message}", result.Message);
                if (!silent && ShowError != null)
                    await ShowError("Deployment Center", $"Update-Prüfung fehlgeschlagen:\n{result.Message}");
                return;
            }

            if (!result.UpdateAvailable)
            {
                Log.Information("Update-Prüfung: v{Version} ist aktuell (Kanal {Channel}).",
                    DcConfig.AppVersion, Options.DcUpdateChannel);
                if (!silent && ShowInfo != null)
                    await ShowInfo("Deployment Center", $"Predictalytics v{DcConfig.AppVersion} ist aktuell.");
                return;
            }

            var latest = result.LatestRelease?.Version ?? "?";
            Log.Warning("⬆ Update verfügbar: v{Latest} (installiert: v{Current}, Kanal {Channel}){Critical}",
                latest, DcConfig.AppVersion, Options.DcUpdateChannel, result.IsCritical ? " — KRITISCH" : "");
            BuildVersionText = $"v{DcConfig.AppVersion} — Update v{latest} verfügbar";

            if (silent && !result.IsCritical) return;

            var notes = result.LatestRelease?.Changelog;
            var agentPresent = DcUpdateService.FindUpdateAgent() is not null;
            var text = $"Neues Release v{latest} verfügbar (installiert: v{DcConfig.AppVersion}).\n" +
                       (result.IsCritical ? "\nDieses Update ist als kritisch markiert.\n" : "") +
                       (string.IsNullOrWhiteSpace(notes) ? "" : $"\n{notes}\n") +
                       (agentPresent
                           ? "\nJetzt installieren? Predictalytics wird dazu beendet."
                           : "\nDer Update-Agent liegt nicht neben der Anwendung — bitte manuell einspielen.");

            if (!agentPresent)
            {
                if (ShowInfo != null) await ShowInfo("Deployment Center", text);
                return;
            }

            if (ShowConfirm == null || !await ShowConfirm("Deployment Center", text)) return;

            // The agent replaces the running installation, so announce the shutdown first —
            // otherwise the monitor reports a crash a few minutes later.
            _heartbeat?.NotifyStopping();
            DcUpdateService.LaunchAgent(Options.DcUpdateChannel);
        }
        catch (Exception ex)
        {
            Log.Warning(ex, "Update-Prüfung fehlgeschlagen");
        }
    }

    [RelayCommand]
    private async Task ShowLicenseStatusAsync()
    {
        if (ShowInfo == null) return;

        var hardware = LicenseGuard.GetHardwareInfo();
        var result = License?.LastResult;

        var grace = result?.CacheExpiresAt is { } expiresAt && expiresAt > 0
            ? $"\nOffline-Gnadenfrist bis: {DateTimeOffset.FromUnixTimeSeconds(expiresAt).UtcDateTime:yyyy-MM-dd HH:mm} UTC"
            : "";

        await ShowInfo("Lizenzstatus",
            $"Produkt: {DcConfig.ProductSlug}\n" +
            $"Version: {DcConfig.AppVersion} ({DcConfig.GitCommitShort})\n" +
            $"Server: {DcConfig.BaseUrl}\n" +
            $"Hardware-ID: {hardware.HardwareId}\n" +
            $"Quelle: {hardware.HwidSource}\n\n" +
            $"Letzte Prüfung: {result?.Status ?? "unbekannt"}" +
            (result?.IsCached == true ? " (aus Offline-Cache)" : "") +
            $"\n{result?.Message}{grace}");
    }

    // ─── Befehle ───

    [RelayCommand]
    private async Task ToggleServerAsync()
    {
        if (!_host.WorkersRunning)
        {
            _workerCts = new CancellationTokenSource();
            Log.Information("🚀 Starte Hintergrund-Worker...");
            try
            {
                await _host.StartWorkersAsync(_workerCts.Token);
            }
            catch (OperationCanceledException) { }
            catch (Exception ex) { Log.Error(ex, "Worker error"); }
        }
        else
        {
            Log.Information("⏹ Stoppe Hintergrund-Worker...");
            _workerCts?.Cancel();
            Log.Information("Worker gestoppt.");
        }
        UpdateStatus();
    }

    [RelayCommand]
    private async Task ToggleWebserverAsync()
    {
        if (!_host.WebServerRunning)
        {
            try
            {
                Log.Information("🌐 Starte Kestrel auf {Url}...", Options.WebserverUrl);
                await _host.StartWebServerAsync();
                Log.Information("✅ WebUI erreichbar unter {Url}", Options.WebserverUrl);
                Log.Information("📄 Swagger unter {Url}/swagger", Options.WebserverUrl);
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Webserver konnte nicht gestartet werden");
                if (ShowError != null) await ShowError("Fehler", $"Webserver konnte nicht gestartet werden:\n{ex.Message}");
            }
        }
        else
        {
            Log.Information("⏹ Stoppe Webserver...");
            await _host.StopWebServerAsync();
            Log.Information("Webserver gestoppt.");
        }
        UpdateStatus();
    }

    [RelayCommand]
    private void SaveSettings()
    {
        Options.Save();
        DcErrorReporter.Configure(Options.DcToken, Options.DcErrorReportingEnabled);
        RestartHeartbeat();
        Log.Information("Einstellungen gespeichert: {Path}", PredictalyticsOptions.SettingsFilePath);
        UpdateStatus();
    }

    [RelayCommand]
    private void OpenWebUi() => OpenInShell(Options.WebserverUrl);

    [RelayCommand]
    private void OpenLogFolder()
    {
        var path = LoggingSetup.DefaultLogDirectory;
        OpenInShell(Directory.Exists(path) ? path : AppContext.BaseDirectory);
    }

    [RelayCommand]
    private async Task SyncMarketsAsync()
    {
        if (!await GuardWorkersStoppedAsync("Der Markt-Sync")) return;

        try
        {
            IsBusy = true;
            Log.Information("Manueller Markt-Sync gestartet...");
            using var cts = new CancellationTokenSource();
            await _host.RunSingleMarketSyncAsync(cts.Token);
            Log.Information("Manueller Markt-Sync abgeschlossen.");
            if (ShowInfo != null) await ShowInfo("Fertig", "Markt-Sync abgeschlossen.");
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Manueller Markt-Sync fehlgeschlagen");
            if (ShowError != null) await ShowError("Fehler", $"Markt-Sync fehlgeschlagen:\n{ex.Message}");
        }
        finally { IsBusy = false; }
    }

    [RelayCommand]
    private async Task UpdateDatabaseAsync()
    {
        if (!await GuardWorkersStoppedAsync("Das Datenbank-Update")) return;

        try
        {
            IsBusy = true;
            Log.Information("Manuelles Datenbank-Update gestartet...");
            await _host.UpdateDatabaseAsync();
            Log.Information("Datenbank aktualisiert.");
            if (ShowInfo != null) await ShowInfo("Fertig", "Datenbank-Update abgeschlossen.");
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Manuelles Datenbank-Update fehlgeschlagen");
            if (ShowError != null) await ShowError("Fehler", $"Datenbank-Update fehlgeschlagen:\n{ex.Message}");
        }
        finally { IsBusy = false; }
    }

    [RelayCommand]
    private async Task RecalculateAllAsync()
    {
        if (!await GuardWorkersStoppedAsync("Die Neuberechnung")) return;

        if (ShowConfirm == null) return;
        var confirmed = await ShowConfirm("Alle Trader neu berechnen",
            "Dies löscht alle ABGELEITETEN Analysedaten (Positionen, Tages-Snapshots, " +
            "Kategorie-Statistiken) und markiert jeden Trader zur vollständigen Neuberechnung.\n\n" +
            "Rohdaten (Trades und Märkte) bleiben unberührt.\n\n" +
            "Danach den Server starten: der Analytics-Worker baut jeden Trader mit der " +
            "aktuellen Engine neu auf (läuft im Hintergrund, kann bei vielen Tradern " +
            "mehrere Stunden dauern).\n\nFortfahren?");
        if (!confirmed) return;

        try
        {
            IsBusy = true;
            Log.Information("Vollständige Neuberechnung angestoßen...");
            using var cts = new CancellationTokenSource();
            var summary = await _host.RunRecalculateAllTradersAsync(cts.Token);
            if (ShowInfo != null)
                await ShowInfo("Alle Trader neu berechnen",
                    $"Zurücksetzen abgeschlossen:\n\n{summary}\n\nJetzt den Server starten, um die Analytik neu aufzubauen.");
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Vollständige Neuberechnung fehlgeschlagen");
            if (ShowError != null) await ShowError("Fehler", $"Neuberechnung fehlgeschlagen:\n{ex.Message}");
        }
        finally { IsBusy = false; }
    }

    // ─── Hilfsfunktionen ───

    private async Task<bool> GuardWorkersStoppedAsync(string action)
    {
        if (!_host.WorkersRunning) return true;
        if (ShowError != null)
            await ShowError("Worker aktiv", $"{action} kann nicht laufen, während die Hintergrund-Worker aktiv sind. Bitte zuerst den Server stoppen.");
        return false;
    }

    private async Task RefreshDbSizeAsync()
    {
        var sizeMb = await _host.GetDatabaseSizeMbAsync();
        _lastDbSizeMb = sizeMb;
        DbSizeText = sizeMb.HasValue ? $"DB Size: {sizeMb.Value:F2} MB" : "DB Size: —";
    }

    /// <summary>
    /// Oeffnet Pfad oder URL mit der Standardanwendung des Systems.
    /// UseShellExecute funktioniert unter Windows wie unter Linux (dort ueber xdg-open).
    /// </summary>
    private static void OpenInShell(string target)
    {
        try
        {
            System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(target) { UseShellExecute = true });
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Konnte {Target} nicht öffnen", target);
        }
    }

    /// <summary>Beim Beenden: Monitor abmelden, Worker und Webserver stoppen.</summary>
    public void Shutdown()
    {
        _heartbeat?.NotifyStopping();
        _heartbeat?.Dispose();
        _heartbeat = null;
        _workerCts?.Cancel();
        try { _host.StopWebServerAsync().GetAwaiter().GetResult(); } catch { /* beendet sich ohnehin */ }
    }
}
