using TeleWave.Domain.Broadcast.Live;
using Xunit;

namespace TeleWave.Domain.Tests.Broadcast;

public class LiveWindowCalculatorTests
{
    private static readonly DateTimeOffset Epoch = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
    private const int Seg = 2;
    private const int Window = 5;

    private static LiveInput Input(
        double nowOffsetSeconds,
        IReadOnlyList<LiveEntry> entries,
        LiveFiller? filler = null
    ) =>
        new(
            Now: Epoch.AddSeconds(nowOffsetSeconds),
            Epoch: Epoch,
            SegmentSeconds: Seg,
            WindowSegments: Window,
            Entries: entries,
            Filler: filler
        );

    [Fact]
    public void SingleEntry_ProducesWindowWithMonotonicMediaSequence()
    {
        var asset = Guid.NewGuid();
        var entries = new List<LiveEntry> { new(Epoch, Epoch.AddSeconds(100), asset, 50) };

        // now = epoch + 20s → currentIndex = 10, окно [6..10].
        var playlist = LiveWindowCalculator.Build(Input(20, entries));

        Assert.Equal(6, playlist.MediaSequence);
        Assert.Equal(Seg, playlist.TargetDuration);
        Assert.Equal(5, playlist.Segments.Count);
        Assert.Equal([6, 7, 8, 9, 10], playlist.Segments.Select(s => s.LocalIndex));
        Assert.All(playlist.Segments, s => Assert.Equal(asset, s.AssetId));
        Assert.DoesNotContain(playlist.Segments, s => s.Discontinuity);
    }

    [Fact]
    public void EntryBoundary_InsertsDiscontinuity()
    {
        Guid a = Guid.NewGuid(),
            b = Guid.NewGuid();
        var entries = new List<LiveEntry>
        {
            new(Epoch, Epoch.AddSeconds(16), a, 8), // g0..7
            new(Epoch.AddSeconds(16), Epoch.AddSeconds(40), b, 12), // g8..
        };

        var playlist = LiveWindowCalculator.Build(Input(20, entries));

        Assert.Equal(6, playlist.MediaSequence);
        Assert.Equal([a, a, b, b, b], playlist.Segments.Select(s => s.AssetId));
        // DISCONTINUITY только перед первым сегментом B (индекс 2 в окне).
        Assert.Equal(
            [false, false, true, false, false],
            playlist.Segments.Select(s => s.Discontinuity)
        );
    }

    [Fact]
    public void Gap_WithFiller_LoopsFiller()
    {
        var filler = new LiveFiller(Guid.NewGuid(), 4);

        var playlist = LiveWindowCalculator.Build(Input(20, [], filler));

        Assert.Equal(6, playlist.MediaSequence);
        Assert.Equal(5, playlist.Segments.Count);
        Assert.Equal([2, 3, 0, 1, 2], playlist.Segments.Select(s => s.LocalIndex));
        Assert.All(playlist.Segments, s => Assert.Equal(filler.AssetId, s.AssetId));
    }

    [Fact]
    public void LeadingGap_WithoutFiller_TrimmedFromWindowStart()
    {
        var b = Guid.NewGuid();
        var entries = new List<LiveEntry>
        {
            new(Epoch.AddSeconds(16), Epoch.AddSeconds(40), b, 12),
        };

        // Окно [6..10]: g6,g7 без записи и без филлера → отброшены, старт с g8.
        var playlist = LiveWindowCalculator.Build(Input(20, entries));

        Assert.Equal(8, playlist.MediaSequence);
        Assert.Equal(3, playlist.Segments.Count);
        Assert.Equal([0, 1, 2], playlist.Segments.Select(s => s.LocalIndex));
        Assert.DoesNotContain(playlist.Segments, s => s.Discontinuity);
    }

    [Fact]
    public void BeforeEpoch_ReturnsEmpty()
    {
        var playlist = LiveWindowCalculator.Build(Input(-10, []));

        Assert.Empty(playlist.Segments);
    }
}
