using System.Runtime.InteropServices;
using Predictalytics.Hosting;
using Serilog;

namespace Predictalytics.Shell.Services;

/// <summary>
/// Betrieb ohne Oberflaeche — fuer Linux-Server ohne Desktop-Session
/// (systemd, Container). Startet Webserver und Worker und laeuft, bis
/// SIGTERM oder Strg+C eintrifft.
/// </summary>
internal static class HeadlessRunner
{
    /// <summary>Umgebungsvariable fuer die Aktivierung ohne Dialog.</summary>
    private const string LicenseKeyVariable = "PREDICTALYTICS_LICENSE_KEY";

    public const int ExitOk = 0;
    public const int ExitNoLicense = 2;
    public const int ExitFailed = 3;

    public static async Task<int> RunAsync()
    {
        RuntimeSetup.UseInvariantCulture();

        var options = PredictalyticsOptions.Load();

        // Kein Terminal-Sink: Ausgabe geht auf die Konsole und in die Logdateien.
        LoggingSetup.Configure();

        DcErrorReporter.Configure(options.DcToken, options.DcErrorReportingEnabled);
        DcErrorReporter.InstallGlobalHandlers();
        Deploymentcenter.Client.LicenseClient.DefaultAppVersion = DcConfig.AppVersion;

        LoggingSetup.LogStartupBanner();
        Log.Information("Headless-Modus. Einstellungen: {Path}", PredictalyticsOptions.SettingsFilePath);

        DcHeartbeatService? heartbeat = null;
        try
        {
            var session = await EnsureLicensedAsync();
            if (session is null) return ExitNoLicense;

            var host = new PredictalyticsHost(options);
            using var cts = new CancellationTokenSource();
            using var shutdownSignals = RegisterShutdown(cts);

            using var licenseWatch = LicenseGuard.StartPeriodicRevalidation(session, result =>
            {
                Log.Fatal("Lizenz nicht mehr gültig ({Status}): {Message} — Dienst wird beendet.",
                    result.Status, result.Message);
                cts.Cancel();
            });

            heartbeat = StartHeartbeat(options, host);

            await host.StartWebServerAsync();
            Log.Information("WebUI erreichbar unter {Url}", options.WebserverUrl);

            // Laeuft, bis abgebrochen wird.
            await host.StartWorkersAsync(cts.Token);

            heartbeat?.NotifyStopping();
            await host.StopWebServerAsync();
            Log.Information("Dienst planmäßig beendet.");
            return ExitOk;
        }
        catch (OperationCanceledException)
        {
            heartbeat?.NotifyStopping();
            Log.Information("Dienst planmäßig beendet.");
            return ExitOk;
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Headless-Betrieb abgebrochen.");
            return ExitFailed;
        }
        finally
        {
            heartbeat?.Dispose();
            await Log.CloseAndFlushAsync();
        }
    }

    /// <summary>
    /// Lizenzpruefung ohne Dialog: zuerst der zwischengespeicherte Schluessel, sonst
    /// einmalige Aktivierung mit dem Schluessel aus der Umgebungsvariable.
    /// </summary>
    private static async Task<LicenseSession?> EnsureLicensedAsync()
    {
        var client = LicenseGuard.CreateClient();

        var check = await LicenseGuard.TryUseCachedAsync(client);
        if (check.Session is not null) return check.Session;

        var key = Environment.GetEnvironmentVariable(LicenseKeyVariable);
        if (string.IsNullOrWhiteSpace(key))
        {
            Log.Fatal("Keine nutzbare Lizenz ({Status}) und {Variable} ist nicht gesetzt. " +
                      "Im Headless-Betrieb gibt es keinen Aktivierungsdialog — bitte den Lizenzschlüssel " +
                      "über die Umgebungsvariable bereitstellen oder einmalig mit --license-set-key aktivieren.",
                check.LastResult?.Status ?? "kein Schlüssel hinterlegt", LicenseKeyVariable);
            return null;
        }

        Log.Information("Aktiviere Lizenz mit dem Schlüssel aus {Variable}…", LicenseKeyVariable);
        var (session, result) = await LicenseGuard.ActivateAsync(client, key);
        if (session is not null)
        {
            Log.Information("Lizenz aktiviert ({Status}).", result.Status);
            return session;
        }

        Log.Fatal("Aktivierung fehlgeschlagen ({Status}): {Message}", result.Status, result.Message);
        return null;
    }

    private static DcHeartbeatService? StartHeartbeat(PredictalyticsOptions options, PredictalyticsHost host)
    {
        if (!options.DcHeartbeatEnabled) return null;
        if (string.IsNullOrWhiteSpace(options.DcToken))
        {
            Log.Information("🐕 Heartbeat ist aktiviert, aber es fehlt das Deployment-Center-Token.");
            return null;
        }

        var heartbeat = new DcHeartbeatService(
            options.DcToken,
            options.DcSource,
            options.DcInstance,
            options.DcHeartbeatIntervalSeconds,
            ct => CollectSnapshotAsync(options, host, ct));
        heartbeat.Start();
        return heartbeat;
    }

    private static async Task<DcHeartbeatSnapshot> CollectSnapshotAsync(
        PredictalyticsOptions options, PredictalyticsHost host, CancellationToken ct)
    {
        var snapshot = new DcHeartbeatSnapshot
        {
            Message = host.WorkersRunning ? "Worker laufen" : "Worker gestoppt"
        };

        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 (!string.IsNullOrWhiteSpace(options.DbName))
        {
            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);

                snapshot.Checks["db"] = new DcCheck(true, $"{started.ElapsedMilliseconds} ms", started.ElapsedMilliseconds);
            }
            catch (Exception ex)
            {
                snapshot.Checks["db"] = new DcCheck(false, ex.Message);
            }
        }

        return snapshot;
    }

    /// <summary>
    /// SIGTERM (systemd stop), SIGINT und Strg+C sauber behandeln, damit der Dienst
    /// nicht hart abgeschossen wird.
    /// </summary>
    private static IDisposable RegisterShutdown(CancellationTokenSource cts)
    {
        var registrations = new List<IDisposable>();

        void Handle(PosixSignalContext context)
        {
            context.Cancel = true;      // Standardverhalten (sofortiges Beenden) unterdruecken
            Log.Information("Signal {Signal} empfangen — fahre herunter…", context.Signal);
            cts.Cancel();
        }

        registrations.Add(PosixSignalRegistration.Create(PosixSignal.SIGTERM, Handle));
        registrations.Add(PosixSignalRegistration.Create(PosixSignal.SIGINT, Handle));

        Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };

        return new CompositeDisposable(registrations);
    }

    private sealed class CompositeDisposable(List<IDisposable> items) : IDisposable
    {
        public void Dispose()
        {
            foreach (var item in items)
            {
                try { item.Dispose(); } catch { /* beendet sich ohnehin */ }
            }
        }
    }
}
