using System.Collections.Concurrent;
using System.Threading.Channels;
using Nexus.Api.Models;

namespace Nexus.Api.Services;

public sealed class LiveUpdateService : ILiveUpdateService
{
    private const int ReplayLimit = 256;

    private readonly ConcurrentDictionary<Guid, Channel<LiveUpdateEnvelope>> _subscribers = new();
    private readonly object _historyLock = new();
    private readonly Queue<LiveUpdateEnvelope> _history = new();
    private long _sequence;

    public long CurrentSequence => Interlocked.Read(ref _sequence);

    public Task<LiveUpdateSubscription> SubscribeAsync(long? afterSequence = null, CancellationToken ct = default)
    {
        var channel = Channel.CreateUnbounded<LiveUpdateEnvelope>(new UnboundedChannelOptions
        {
            SingleReader = true,
            SingleWriter = false,
            AllowSynchronousContinuations = false
        });

        var id = Guid.NewGuid();
        _subscribers[id] = channel;

        var replay = afterSequence.HasValue ? GetReplay(afterSequence.Value) : Array.Empty<LiveUpdateEnvelope>();
        foreach (var envelope in replay)
        {
            channel.Writer.TryWrite(envelope);
        }

        ct.Register(() =>
        {
            if (_subscribers.TryRemove(id, out var removed))
            {
                removed.Writer.TryComplete();
            }
        });

        return Task.FromResult(new LiveUpdateSubscription
        {
            Reader = channel.Reader,
            StartingSequence = replay.LastOrDefault()?.Sequence ?? CurrentSequence
        });
    }

    public LiveUpdateEnvelope Publish(string type, object payload, string channel = "dashboard")
    {
        var envelope = new LiveUpdateEnvelope(
            type,
            DateTimeOffset.UtcNow,
            payload,
            Interlocked.Increment(ref _sequence),
            channel);

        lock (_historyLock)
        {
            _history.Enqueue(envelope);
            while (_history.Count > ReplayLimit)
            {
                _history.Dequeue();
            }
        }

        foreach (var (id, subscriber) in _subscribers)
        {
            if (!subscriber.Writer.TryWrite(envelope) && _subscribers.TryRemove(id, out var removed))
            {
                removed.Writer.TryComplete();
            }
        }

        return envelope;
    }

    private LiveUpdateEnvelope[] GetReplay(long afterSequence)
    {
        lock (_historyLock)
        {
            return _history.Where(item => item.Sequence > afterSequence).ToArray();
        }
    }
}
