using AyCode.Core.Enums;
using AyCode.Core.Helpers;
using AyCode.Core.Loggers;
using AyCode.Entities;
using AyCode.Entities.LogItems;
using AyCode.Services.SignalRs;
using Microsoft.AspNetCore.SignalR.Client;

namespace AyCode.Services.Loggers
{
    /// <summary>
    /// Nem-generikus, JSON-protokollos alak — back-compat a meglévő fogyasztóknak
    /// (wire-típus: AcLogItemClient; a szerver-oldali párja az AcLoggerSignalRHub&lt;TLogger&gt; shim).
    /// </summary>
    public class AcSignaRClientLogItemWriter : AcSignaRClientLogItemWriter<AcLogItemClient>
    {
        public AcSignaRClientLogItemWriter(string fullHubName, AppType appType = AppType.Web, LogLevel logLevel = LogLevel.Detail, string? categoryName = null)
            : base(fullHubName, appType, logLevel, categoryName)
        { }
    }

    /// <summary>
    /// SignalR log-item feltöltő writer, generikus wire-típussal. A <typeparamref name="TLogItem"/>-et a fogyasztó
    /// adja (tipikusan egy [AcBinarySerializable]-lel jelölt AcLogItemClient-leszármazott — így a csatorna
    /// AcBinary-protokollal mehet; a generált serializer a fogyasztó rétegében él, az AyCode.Entities generator-mentes marad).
    /// Az opcionális acBinaryProtocolOptions-szel a kapcsolat az AcBinary protokollt használja;
    /// enélkül a SignalR default JSON-ja megy (back-compat).
    /// </summary>
    public class AcSignaRClientLogItemWriter<TLogItem> : AcLogItemWriterBase<TLogItem>, IAcLogWriterClientBase
        where TLogItem : class, IAcLogItemClient
    {
        private readonly HubConnection _hubConnection;
        private readonly string _fullHubName;

        // A log-csatorna a SAJÁT hibáit nem logolhatja önmagán át (rekurzió) → Console.Error.
        // Spam-védelem: hiba-állapotonként EGY jelentés; sikeres küldés után a kapu újraélesedik, és
        // helyreálláskor kiírjuk, hány log-item veszett el. (Egyszerű mezők — a torz dupla-jelentés ártalmatlan.)
        private bool _failureReported;
        private bool _connectedReported;
        private long _droppedCount;

        public AcSignaRClientLogItemWriter(string fullHubName, AppType appType = AppType.Web, LogLevel logLevel = LogLevel.Detail, string? categoryName = null, AcBinaryHubProtocolOptions? acBinaryProtocolOptions = null) : base(appType, logLevel, categoryName)
        {
            _fullHubName = fullHubName;

            // VISSZACSATOLÁS-VÉDELEM: erre a builderre TILOS app-loggert kötni (AddAcDefaults / ConfigureLogging) —
            // a protokoll saját logja a feltöltőn át újra ide érkezne (log → sorosítás → log → … robbanó hurok).
            // AcBinary-nál ezért a hívó az opciókban EXPLICIT süket loggert adjon (pl. NullLogger.Instance),
            // hogy az AddAcBinaryProtocol ??= DI-fallbackje se tölthesse fel.
            var hubBuilder = new HubConnectionBuilder().WithUrl(fullHubName);

            if (acBinaryProtocolOptions != null) hubBuilder.AddAcBinaryProtocol(acBinaryProtocolOptions);

            _hubConnection = hubBuilder
                //.WithAutomaticReconnect()
                .Build();

            StartConnection().Forget();
        }

        /// <summary>NEM dob: a kapcsolódási hibát maga jelenti (ReportFailureOnce) — a hívók (ctor fire-and-forget,
        /// WriteLogItemCallback) a State-ből látják az eredményt.</summary>
        public async Task StartConnection()
        {
            try
            {
                if (_hubConnection.State == HubConnectionState.Disconnected)
                    await _hubConnection.StartAsync();

                if (_hubConnection.State != HubConnectionState.Connected)
                    await TaskHelper.WaitToAsync(() => _hubConnection.State == HubConnectionState.Connected, 10000, 10, 25);

                // Felépülés-marker (kapcsolatonként egyszer) — enélkül a „minden csendben OK" és a „soha el sem indult"
                // megkülönböztethetetlen a konzolból.
                if (_hubConnection.State == HubConnectionState.Connected && !_connectedReported)
                {
                    _connectedReported = true;
                    Console.WriteLine($"[{nameof(AcSignaRClientLogItemWriter)}] connected; url: {_fullHubName}");
                }
            }
            catch (Exception ex)
            {
                ReportFailureOnce("connect failed", ex);
            }
        }

        public async Task StopConnection()
        {
            await _hubConnection.StopAsync();
            await _hubConnection.DisposeAsync();
        }

        public override void Write(AppType appType, LogLevel logLevel, string? logText, string? callerMemberName, string? categoryName, string? errorType, string? exMessage)
            => WriteLogItem(CreateLogItem(DateTime.UtcNow, appType, Environment.CurrentManagedThreadId, logLevel, logText, callerMemberName, categoryName, errorType, exMessage));

        protected override async void WriteLogItemCallback(TLogItem logItem)
        {
            // Teljes try/catch: async void — egy StartAsync/SendAsync-kivétel különben kezeletlenül szökne el.
            try
            {
                //Ez fontos, mert a signalR nem küldi a DateTime.Kind-ot! A szeró oldalon kap egy utc DateTime-ot, ami a kliens lokális DateTime-ját tartalmazza! - J.
                logItem.TimeStampUtc = logItem.TimeStampUtc.ToUniversalTime();

                await StartConnection();

                if (_hubConnection.State != HubConnectionState.Connected)
                {
                    _droppedCount++;
                    ReportFailureOnce($"connection not available ({_hubConnection.State}), log item DROPPED", null);
                    return;
                }

                //var refreshToken = Setting.UserBasicDetails?.RefreshToken ?? Guid.NewGuid().ToString("N");
                await _hubConnection.SendAsync("AddLogItem", /*refreshToken,*/ logItem);

                ReportRecoveryIfNeeded();
            }
            catch (Exception ex)
            {
                _droppedCount++;
                ReportFailureOnce("send failed, log item DROPPED", ex);
            }
        }

        /// <summary>Hiba-állapotonként EGYSZER ír a Console.Error-ra (nem spamel) — a következő sikeres küldés élesíti újra.</summary>
        private void ReportFailureOnce(string message, Exception? ex)
        {
            _connectedReported = false; // a következő sikeres felépülés újra jelentkezhessen

            if (_failureReported) return;
            _failureReported = true;

            Console.Error.WriteLine($"[{nameof(AcSignaRClientLogItemWriter)}] {message}; url: {_fullHubName}; droppedCount: {_droppedCount}{(ex == null ? string.Empty : $"; ex: {ex.GetType().Name}: {ex.Message}")}");
        }

        /// <summary>Sikeres küldésnél: ha hiba-állapotból jöttünk vissza, jelentjük a kiesés alatt elveszett logok számát.</summary>
        private void ReportRecoveryIfNeeded()
        {
            if (!_failureReported) return;
            _failureReported = false;

            Console.Error.WriteLine($"[{nameof(AcSignaRClientLogItemWriter)}] connection recovered; url: {_fullHubName}; DROPPED items while down: {_droppedCount}");
            _droppedCount = 0;
        }
    }

    
}
