using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using PolyTrader.Core;
using PolyTrader.Core.Configuration;
using PolyTrader.Core.Security;

namespace PolyTraderSharp.Services
{
    /// <summary>
    /// Meldet ungefangene Fehler an den Fehler-Stream des Deploymentcenters (D-3,
    /// <c>POST /api/errors/v1/report</c>). Fängt drei Quellen ab: <see cref="AppDomain.UnhandledException"/>
    /// (der Prozess stirbt gerade – <c>level: "fatal"</c>), <see cref="TaskScheduler.UnobservedTaskException"/>
    /// (ein Hintergrund-Task ist fehlgeschlagen, ohne dass jemand hinsah – <c>"warning"</c>) und
    /// <see cref="TerminalLogger.OnLogMessage"/> bei <see cref="LogLevel.Error"/> (<c>"error"</c>).
    ///
    /// **Grundregel wie beim Watchdog:** Ein nicht erreichbares Deploymentcenter darf PolyTrader nie
    /// beeinträchtigen. Zusätzlich hier besonders wichtig: **keine Rückkopplung.** Der Reporter selbst
    /// darf einen Sendefehler NIEMALS über <see cref="TerminalLogger.Error"/> melden – das würde exakt
    /// den Log-Kanal auslösen, den er selbst abhört, und sich damit endlos selbst melden. Eigene
    /// Fehlermeldungen laufen deshalb ausschließlich über <see cref="TerminalLogger.Warning"/>.
    ///
    /// Fehlermeldungen enthalten möglicherweise Wallet-Adressen, Marktkennungen oder Beträge; der
    /// Deploymentcenter-Server liegt beim Hoster. Nachricht und Stacktrace laufen deshalb vor dem
    /// Versand durch <see cref="SecretRedactor"/>.
    /// </summary>
    public sealed class DeploymentcenterErrorReporter : BackgroundService
    {
        /// <summary>
        /// Eigene, deutlich strengere Grenze als das serverseitige Limit (60/Minute/IP) – ein
        /// durchdrehender Fehlerpfad soll lokal gedrosselt werden, bevor er den Server überhaupt
        /// erreicht oder mit anderen Diensten auf derselben IP kollidiert.
        /// </summary>
        private const int MaxReportsPerMinute = 20;

        private const int MaxStackTraceLength = 4000;

        private readonly TerminalLogger _logger;
        private readonly DeploymentcenterOptions _options;
        private readonly HttpClient _http;

        private readonly object _rateLock = new();
        private readonly Queue<DateTime> _recentSendsUtc = new();
        private bool _throttleWarningLogged;

        public DeploymentcenterErrorReporter(
            TerminalLogger logger,
            DeploymentcenterOptions options,
            HttpClient? httpClient = null)
        {
            _logger = logger;
            _options = options;
            _http = httpClient ?? new HttpClient(new SocketsHttpHandler
            {
                PooledConnectionLifetime = TimeSpan.FromMinutes(2)
            })
            {
                Timeout = TimeSpan.FromSeconds(10)
            };

            _logger.OnLogMessage += OnAppLog;
            AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
            TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
        }

        /// <summary>Rein ereignisgesteuert – kein eigener Takt nötig, nur Auf- und Abbau der Abonnements.</summary>
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            try
            {
                await Task.Delay(Timeout.Infinite, stoppingToken).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                // Normales Herunterfahren.
            }
        }

        public override void Dispose()
        {
            _logger.OnLogMessage -= OnAppLog;
            AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
            TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
            base.Dispose();
        }

        internal void OnAppLog(object? sender, LogMessageEventArgs e)
        {
            if (e.Level != LogLevel.Error) return;
            // Fire-and-forget: OnLogMessage ist synchron, darf den loggenden Thread nicht blockieren.
            // ReportAsync fängt jede eigene Ausnahme selbst ab (No-Throw-Vertrag) – hier entsteht also
            // keine unbeobachtete Task-Exception, die wiederum den UnobservedTaskException-Zweig
            // triggern und sich selbst melden würde.
            _ = ReportAsync("ApplicationError", e.Message, stackTrace: null, level: "error");
        }

        internal void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            string message = (e.ExceptionObject as Exception)?.Message ?? e.ExceptionObject?.ToString() ?? "Unbekannter Fehler";
            string? stack = (e.ExceptionObject as Exception)?.StackTrace;
            string type = (e.ExceptionObject as Exception)?.GetType().FullName ?? "UnknownException";

            // Der Prozess beendet sich gleich (IsTerminating in aller Regel true) - synchron und mit
            // kurzer Frist senden, statt auf einen Fire-and-forget-Task zu setzen, der nie mehr laufen
            // könnte.
            try
            {
                using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
                ReportAsync(type, message, stack, "fatal", cts.Token).GetAwaiter().GetResult();
            }
            catch { /* Der Prozess stirbt ohnehin - hier ist nichts mehr zu retten. */ }
        }

        internal void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
        {
            var ex = e.Exception?.Flatten().InnerException ?? (Exception?)e.Exception;
            _ = ReportAsync(ex?.GetType().FullName ?? "UnobservedTaskException",
                ex?.Message ?? "Unbeobachtete Task-Exception", ex?.StackTrace, "warning");

            // Verhindert, dass .NET den Prozess wegen der unbeobachteten Exception beendet - wir haben
            // sie ja gerade beobachtet und gemeldet.
            e.SetObserved();
        }

        /// <summary>
        /// Meldet einen Fehler. Wirft nie, meldet eigene Fehlschläge nur über
        /// <see cref="TerminalLogger.Warning"/> (siehe Klassendokumentation zur Rückkopplung).
        /// </summary>
        public async Task<bool> ReportAsync(
            string exceptionType, string message, string? stackTrace, string level, CancellationToken ct = default)
        {
            try
            {
                if (!_options.ErrorReportingEnabled) return false;
                if (string.IsNullOrWhiteSpace(_options.BaseUrl) || string.IsNullOrWhiteSpace(_options.Token))
                    return false;

                if (!TryBuildEndpoint(_options.BaseUrl, out Uri? uri, out string urlProblem))
                {
                    WarnOnce($"⚠️ Fehler-Reporting nicht sendebereit: {urlProblem}");
                    return false;
                }

                if (!TryReserveRateSlot())
                {
                    if (!_throttleWarningLogged)
                    {
                        _logger.Warning("⚠️ Fehler-Reporting lokal gedrosselt (mehr als " +
                                        $"{MaxReportsPerMinute} Meldungen/Minute) – weitere Meldungen werden " +
                                        "verworfen, bis sich die Rate beruhigt. Details bleiben im Terminal-Log.");
                        _throttleWarningLogged = true;
                    }
                    return false;
                }

                string token;
                try { token = SecretProtection.Unprotect(_options.Token); }
                catch (Exception ex) { WarnOnce($"⚠️ Fehler-Reporting-Token nicht lesbar: {ex.Message}"); return false; }

                var payload = new
                {
                    project_slug = _options.ProjectSlug,
                    exception = exceptionType,
                    message = WatchdogHeartbeatService.Sanitize(message),
                    stack_trace = SanitizeStackTrace(stackTrace),
                    level,
                    build = BuildInfo.Version,
                    environment = Environment.GetEnvironmentVariable("POLYTRADER_ENVIRONMENT") ?? "production"
                };

                using var request = new HttpRequestMessage(HttpMethod.Post, uri) { Content = JsonContent.Create(payload) };
                request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

                using var response = await _http.SendAsync(request, ct).ConfigureAwait(false);
                if (!response.IsSuccessStatusCode)
                {
                    WarnOnce($"⚠️ Fehler-Reporting fehlgeschlagen: HTTP {(int)response.StatusCode} {response.ReasonPhrase}.");
                    return false;
                }

                return true;
            }
            catch (OperationCanceledException)
            {
                return false;
            }
            catch (Exception ex)
            {
                // Letztes Sicherheitsnetz - siehe Klassendokumentation: NIE über .Error loggen.
                WarnOnce($"⚠️ Fehler-Reporting unerwartet fehlgeschlagen: {ex.Message}");
                return false;
            }
        }

        /// <summary>Entprellt Warnungen wie der Watchdog: der erste Fehlschlag wird gemeldet, nicht jeder einzelne.</summary>
        private bool _lastSendFailed;
        private void WarnOnce(string message)
        {
            if (_lastSendFailed) return;
            _logger.Warning(message);
            _lastSendFailed = true;
        }

        /// <summary>Gleitendes Ein-Minuten-Fenster, ohne Hintergrund-Timer.</summary>
        private bool TryReserveRateSlot()
        {
            lock (_rateLock)
            {
                DateTime now = DateTime.UtcNow;
                while (_recentSendsUtc.Count > 0 && (now - _recentSendsUtc.Peek()) > TimeSpan.FromMinutes(1))
                    _recentSendsUtc.Dequeue();

                if (_recentSendsUtc.Count >= MaxReportsPerMinute) return false;

                _recentSendsUtc.Enqueue(now);
                _throttleWarningLogged = false;   // erneutes Überschreiten wieder genau einmal melden
                return true;
            }
        }

        /// <summary>
        /// Stacktraces dürfen (anders als die kurze Statuszeile im Heartbeat) mehrzeilig bleiben –
        /// sie sind sonst wertlos. Nur die bekannten Secret-Muster werden entfernt, dazu eine
        /// großzügige Längenbegrenzung gegen ausufernde Payloads.
        /// </summary>
        private static string? SanitizeStackTrace(string? stackTrace)
        {
            if (string.IsNullOrEmpty(stackTrace)) return null;
            string s = SecretRedactor.RemoveSecrets(stackTrace);
            return s.Length > MaxStackTraceLength ? s.Substring(0, MaxStackTraceLength - 1) + "…" : s;
        }

        private static bool TryBuildEndpoint(string baseUrl, out Uri? uri, out string problem)
        {
            uri = null;
            problem = string.Empty;

            if (!Uri.TryCreate(baseUrl?.Trim().TrimEnd('/'), UriKind.Absolute, out Uri? parsed))
            {
                problem = $"Deploymentcenter-URL ist keine gültige absolute URL: '{baseUrl}'.";
                return false;
            }

            bool isHttps = parsed.Scheme == Uri.UriSchemeHttps;
            bool isLocalHttp = parsed.Scheme == Uri.UriSchemeHttp && parsed.IsLoopback;
            if (!isHttps && !isLocalHttp)
            {
                problem = $"Deploymentcenter-URL muss HTTPS verwenden (http:// nur für localhost): '{baseUrl}'.";
                return false;
            }

            uri = new Uri(parsed, $"{parsed.AbsolutePath.TrimEnd('/')}/api/errors/v1/report");
            return true;
        }
    }
}
