using System.Threading.Tasks.Dataflow;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Nostr.Client.Json;
using Nostr.Client.Messages;
using Nostr.Client.Requests;
using NostrServices.Services.EventHandlers;

namespace NostrServices.Services;

public class RelayListener : IHostedService
{
    public static readonly NostrKind[] AcceptedKinds =
    [
        NostrKind.Metadata,
        NostrKind.ShortTextNote,
        NostrKind.Zap,
        NostrKind.GenericRepost,
        NostrKind.LiveEvent,
        NostrKind.LiveChatMessage,
        (NostrKind)1_313, // stream clip
        NostrKind.LongFormContent,
        NostrKind.ClassifiedListing,
        NostrKind.RelayListMetadata,
        NostrKind.Contacts
    ];

    private readonly NostrListener _nostr;
    private readonly IMemoryCache _cache;
    private readonly CancellationTokenSource _cts = new();
    private readonly BufferBlock<NostrEvent> _queue = new();
    private readonly ILogger<RelayListener> _logger;
    private readonly IEnumerable<IEventHandler> _handlers;

    public RelayListener(NostrListener nostr, IMemoryCache cache, ILogger<RelayListener> logger, IEnumerable<IEventHandler> handlers)
    {
        _nostr = nostr;
        _cache = cache;
        _logger = logger;
        _handlers = handlers;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        const string subId = "notifications";
        var evs = _nostr.Streams.EventStream.Subscribe(a =>
        {
            if (a.Subscription == subId)
            {
                _queue.Post(a.Event!);
            }
        });

        _nostr.RegisterFilter(subId, new NostrFilter()
        {
            Kinds = AcceptedKinds,
            Limit = 0
        });

        _cts.Token.Register(() => { evs.Dispose(); });
        _ = HandleEvents();
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _cts.Cancel();
        return Task.CompletedTask;
    }

    private async Task HandleEvents()
    {
        while (!_cts.IsCancellationRequested)
        {
            try
            {
                var msg = await _queue.ReceiveAsync();
                const int eventWindowMinutes = 10;
                if (Math.Abs((DateTime.UtcNow - msg.CreatedAt!.Value).TotalMinutes) > eventWindowMinutes)
                {
                    // skip events older/newer than 10mins from now
                    continue;
                }

                var seenKey = $"seen-{msg.Id}";
                var hasSeen = _cache.Get(seenKey);
                if (hasSeen != default) continue;

                _logger.LogDebug(JsonConvert.SerializeObject(msg, NostrSerializer.Settings));
                _cache.Set(seenKey, true, TimeSpan.FromMinutes(eventWindowMinutes));

                foreach (var eventHandler in _handlers)
                {
                    try
                    {
                        await eventHandler.HandleEvent(msg);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Handler failed {msg}", ex.Message);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in handle events {msg}", ex.Message);
            }
        }
    }
}
