using NSubstitute;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Media.Stats;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Media;
using Xunit;

namespace TeleWave.Application.Tests.Media;

public class MediaStatsTests
{
    private static MediaAsset Pending(string name) =>
        MediaAsset.Register(name, ".mkv", MediaSource.Upload);

    private static MediaAsset Processing(string name)
    {
        var a = Pending(name);
        a.MarkProcessing();
        return a;
    }

    private static MediaAsset Ready(string name)
    {
        var a = Pending(name);
        a.MarkProcessing();
        a.MarkReady(
            new MediaReadyInfo(
                TimeSpan.FromMinutes(20),
                2,
                600,
                1920,
                1080,
                "h264",
                "aac",
                "assets/x"
            )
        );
        return a;
    }

    [Fact]
    public async Task Stats_CountsQueuedAndProcessing_AndAveragesReady_ExcludingGenerated()
    {
        var fixture = new TestDb();
        await using (var seed = fixture.New())
        {
            seed.MediaAssets.AddRange(
                Pending("a.mkv"),
                Pending("b.mkv"),
                Processing("c.mkv"),
                Ready("d.mkv"),
                MediaAsset.RegisterGenerated("Заставка") // Generated — не входит в статистику
            );
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var stats = await new GetMediaStatsQueryHandler(db, Limits(1)).Handle(
            new GetMediaStatsQuery(),
            CancellationToken.None
        );

        Assert.Equal(2, stats.Queued); // два Pending (Generated исключён)
        Assert.Equal(1, stats.Processing);
        Assert.NotNull(stats.AverageProcessingSeconds);
        Assert.True(stats.AverageProcessingSeconds >= 0);
    }

    [Fact]
    public async Task Estimate_ScalesWithQueueAndParallelism()
    {
        var fixture = new TestDb();
        await using (var seed = fixture.New())
        {
            // Одна завершённая задаёт среднее, четыре ждут своей очереди.
            seed.MediaAssets.AddRange(
                Ready("done.mkv"),
                Pending("a.mkv"),
                Pending("b.mkv"),
                Pending("c.mkv"),
                Pending("d.mkv")
            );
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var single = await new GetMediaStatsQueryHandler(db, Limits(1)).Handle(
            new GetMediaStatsQuery(),
            CancellationToken.None
        );
        var parallel = await new GetMediaStatsQueryHandler(db, Limits(2)).Handle(
            new GetMediaStatsQuery(),
            CancellationToken.None
        );

        var average = single.AverageProcessingSeconds!.Value;
        Assert.Equal(4 * average, single.EstimatedRemainingSeconds!.Value, 3);
        // Два транскода одновременно — вдвое меньше ждать.
        Assert.Equal(
            single.EstimatedRemainingSeconds!.Value / 2,
            parallel.EstimatedRemainingSeconds!.Value,
            3
        );
    }

    [Fact]
    public async Task Estimate_IsAbsent_WhenQueueIsEmpty()
    {
        var fixture = new TestDb();
        await using (var seed = fixture.New())
        {
            seed.MediaAssets.Add(Ready("done.mkv"));
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var stats = await new GetMediaStatsQueryHandler(db, Limits(1)).Handle(
            new GetMediaStatsQuery(),
            CancellationToken.None
        );

        // Ждать нечего — оценки нет вовсе, а не «0 секунд».
        Assert.Null(stats.EstimatedRemainingSeconds);
    }

    private static IMediaProcessingLimits Limits(int parallel)
    {
        var limits = Substitute.For<IMediaProcessingLimits>();
        limits.MaxParallelTranscodes.Returns(parallel);
        return limits;
    }
}
