using System.Collections.Generic;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using ErsatzTV.Application;
using ErsatzTV.Application.Playouts;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Locking;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Tests.Support;
using NSubstitute;
using NUnit.Framework;
using Shouldly;
using Channel = System.Threading.Channels.Channel;

namespace ErsatzTV.Tests.Application.Playouts;

[TestFixture]
public class ResetAllPlayoutsHandlerTests
{
    private InMemoryTvContext _db = null!;
    private Channel<IBackgroundServiceRequest> _worker = null!;
    private IEntityLocker _entityLocker = null!;

    [SetUp]
    public async Task SetUp()
    {
        _db = await InMemoryTvContext.CreateAsync();
        _worker = Channel.CreateUnbounded<IBackgroundServiceRequest>();
        _entityLocker = Substitute.For<IEntityLocker>();
    }

    [TearDown]
    public async Task TearDown() => await _db.DisposeAsync();

    private ResetAllPlayoutsHandler CreateHandler() =>
        new(_entityLocker, _worker.Writer, _db.Factory);

    private async Task<int> SeedPlayout(PlayoutScheduleKind kind)
    {
        await using TvContext context = _db.CreateContext();
        var playout = new Playout { ChannelId = 0, ScheduleKind = kind };
        context.Playouts.Add(playout);
        await context.SaveChangesAsync();
        return playout.Id;
    }

    [Test]
    public async Task Handle_Should_Queue_Eligible_And_Report_Skipped()
    {
        int classicId = await SeedPlayout(PlayoutScheduleKind.Classic);
        int blockId = await SeedPlayout(PlayoutScheduleKind.Block);
        int lockedId = await SeedPlayout(PlayoutScheduleKind.Sequential);
        int externalJsonId = await SeedPlayout(PlayoutScheduleKind.ExternalJson);
        int noneId = await SeedPlayout(PlayoutScheduleKind.None);

        _entityLocker.IsPlayoutLocked(lockedId).Returns(true);

        ResetAllPlayoutsResult result =
            await CreateHandler().Handle(new ResetAllPlayouts(), CancellationToken.None);

        // eligible, unlocked playouts are queued
        result.QueuedPlayoutIds.ShouldBe(new List<int> { classicId, blockId }, ignoreOrder: true);

        // locked playout lands in SkippedLocked, not queued
        result.SkippedLocked.ShouldBe(new List<int> { lockedId });

        // ExternalJson + None land in SkippedUnsupported
        result.SkippedUnsupported.ShouldBe(new List<int> { externalJsonId, noneId }, ignoreOrder: true);

        // exactly one BuildPlayout message per queued playout was enqueued
        var enqueued = new List<int>();
        while (_worker.Reader.TryRead(out IBackgroundServiceRequest? request))
        {
            var build = request.ShouldBeOfType<BuildPlayout>();
            enqueued.Add(build.PlayoutId);
        }

        enqueued.ShouldBe(new List<int> { classicId, blockId }, ignoreOrder: true);
    }
}
