using FASTER.core;
using Nostr.Client.Messages;
using Nostr.Client.Requests;
using NostrRelay;

namespace FASTERlay;

public class FasterRelay : INostrRelay, IDisposable
{
    private readonly ClientSession<byte[], byte[], byte[], byte[], Empty, SimpleFunctions<byte[], byte[]>> _session;
    private readonly ClientSession<string, byte[], byte[], byte[], Empty, TagSetFunctions> _tagsSession;

    public FasterRelay(NostrStore store)
    {
        _session = store.MainStore.For(new SimpleFunctions<byte[], byte[]>()).NewSession<SimpleFunctions<byte[], byte[]>>();
        _tagsSession = store.TagStore.For(new TagSetFunctions()).NewSession<TagSetFunctions>();
    }

    public ValueTask<bool> AcceptConnection(NostrClientContext context)
    {
        return ValueTask.FromResult(true);
    }

    public async IAsyncEnumerable<NostrEvent> HandleRequest(NostrClientContext context, NostrRequest req)
    {
        var iter = _session.Iterate();
        var filter = req.NostrFilter;
        while (iter.GetNext(out _, out _, out var bytes))
        {
            var ev = NostrBuf.Decode(bytes);
            if (ev != default)
            {
                if (filter.Since.HasValue && ev.CreatedAt <= filter.Since)
                {
                    continue;
                }

                if (filter.Until.HasValue && ev.CreatedAt > filter.Until)
                {
                    continue;
                }

                if (filter.Ids != default && !filter.Ids.Contains(ev.Id!))
                {
                    continue;
                }

                if (filter.Authors != default && !filter.Authors.Contains(ev.Pubkey!))
                {
                    continue;
                }

                if (filter.Kinds != default && !filter.Kinds.Contains(ev.Kind))
                {
                    continue;
                }

                yield return ev;
            }
        }
    }

    public async ValueTask<HandleEventResponse> HandleEvent(NostrClientContext context, NostrEvent ev)
    {
        await SaveEvent(ev);
        return new(true, null);
    }

    public void Dispose()
    {
        _session.Dispose();
        _tagsSession.Dispose();
    }

    private async Task SaveEvent(NostrEvent ev)
    {
        var idBytes = Convert.FromHexString(ev.Id!);
        var eventBytes = NostrBuf.Encode(ev);

        await _session.UpsertAsync(ref idBytes, ref eventBytes);

        var indexTags = ev.Tags!.Where(a => a.TagIdentifier.Length == 1);
        foreach (var tag in indexTags)
        {
            var kIndex = $"{tag.TagIdentifier}.{tag.AdditionalData[0]}";
            await _tagsSession.RMWAsync(ref kIndex, ref idBytes);
        }

        await _session.CompletePendingAsync();
        await _tagsSession.CompletePendingAsync();
    }

    private async Task<NostrEvent?> GetEvent(string id)
    {
        var idBytes = Convert.FromHexString(id);
        var read = await _session.ReadAsync(ref idBytes);
        if (read.Status.Found)
        {
            var data = read.Complete().output;
            return NostrBuf.Decode(data);
        }

        return default;
    }

    private class TagSetFunctions : SimpleFunctions<string, byte[]>
    {
        public TagSetFunctions() : base((l, r) =>
        {
            var output = new byte[l.Length + r.Length];
            Buffer.BlockCopy(l, 0, output, 0, l.Length);
            Buffer.BlockCopy(r, 0, output, l.Length, r.Length);
            return output;
        })
        {
        }
    }
}
