using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.ProgramSchedules;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Scheduling;
using MediatR;
using NSubstitute;
using NUnit.Framework;
using Shouldly;

namespace ErsatzTV.Tests.Application.ProgramSchedules;

[TestFixture]
public class GetProgramScheduleItemsWithDurationsHandlerTests
{
    private IMediaCollectionRepository _mediaCollectionRepository = null!;
    private IMediator _mediator = null!;

    [SetUp]
    public void SetUp()
    {
        _mediator = Substitute.For<IMediator>();
        _mediaCollectionRepository = Substitute.For<IMediaCollectionRepository>();
    }

    [Test]
    public async Task Handle_Should_Estimate_Items_And_Total_From_Collection_Durations()
    {
        ProgramScheduleItemViewModel one = MakeOneItem(1, 10);
        ProgramScheduleItemViewModel multipleCount = MakeMultipleItem(2, 10, MultipleMode.Count, "3");
        ProgramScheduleItemViewModel multipleCollectionSize = MakeMultipleItem(3, 20, MultipleMode.CollectionSize, "0");
        ProgramScheduleItemViewModel duration = MakeDurationItem(4, TimeSpan.FromMinutes(45));
        ProgramScheduleItemViewModel flood = MakeFloodItem(5, 10);
        ProgramScheduleItemViewModel remote = MakeOneItem(6, 30);

        _mediator.Send(Arg.Is<GetProgramScheduleItems>(q => q.Id == 99), Arg.Any<CancellationToken>())
            .Returns([one, multipleCount, multipleCollectionSize, duration, flood, remote]);
        _mediaCollectionRepository.GetItems(10)
            .Returns([MakeMovie(30), MakeMovie(60), MakeMovie(0)]);
        _mediaCollectionRepository.GetItems(20)
            .Returns([MakeMovie(10), MakeMovie(20)]);
        _mediaCollectionRepository.GetItems(30)
            .Returns([MakeRemoteStreamWithFallbackDuration(20)]);

        var handler = new GetProgramScheduleItemsWithDurationsHandler(_mediator, _mediaCollectionRepository);

        ProgramScheduleItemsWithDurationViewModel result =
            await handler.Handle(new GetProgramScheduleItemsWithDurations(99), CancellationToken.None);

        result.Items[0].DurationEstimate.ShouldBe(TimeSpan.FromMinutes(45));
        result.Items[1].DurationEstimate.ShouldBe(TimeSpan.FromMinutes(135));
        result.Items[2].DurationEstimate.ShouldBe(TimeSpan.FromMinutes(30));
        result.Items[3].DurationEstimate.ShouldBe(TimeSpan.FromMinutes(45));
        result.Items[4].DurationEstimate.ShouldBeNull();
        result.Items[5].DurationEstimate.ShouldBe(TimeSpan.FromMinutes(20));
        result.TotalDurationEstimate.ShouldBe(TimeSpan.FromMinutes(275));
    }

    [Test]
    public async Task Handle_Should_Return_Null_Total_When_No_Items_Can_Be_Estimated()
    {
        ProgramScheduleItemViewModel one = MakeOneItem(1, 10);
        ProgramScheduleItemViewModel flood = MakeFloodItem(2, 10);

        _mediator.Send(Arg.Is<GetProgramScheduleItems>(q => q.Id == 99), Arg.Any<CancellationToken>())
            .Returns([one, flood]);
        _mediaCollectionRepository.GetItems(10)
            .Returns([MakeMovie(0)]);

        var handler = new GetProgramScheduleItemsWithDurationsHandler(_mediator, _mediaCollectionRepository);

        ProgramScheduleItemsWithDurationViewModel result =
            await handler.Handle(new GetProgramScheduleItemsWithDurations(99), CancellationToken.None);

        result.Items.ShouldAllBe(item => item.DurationEstimate == null);
        result.TotalDurationEstimate.ShouldBeNull();
    }

    private static ProgramScheduleItemOneViewModel MakeOneItem(int id, int collectionId) =>
        new(
            id,
            id,
            StartType.Dynamic,
            null,
            null,
            CollectionType.Collection,
            MakeCollection(collectionId),
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            PlaybackOrder.Shuffle,
            MarathonGroupBy.None,
            false,
            false,
            null,
            FillWithGroupMode.None,
            null,
            GuideMode.Normal,
            null,
            null,
            null,
            null,
            null,
            [],
            [],
            null,
            null,
            null,
            null);

    private static ProgramScheduleItemMultipleViewModel MakeMultipleItem(
        int id,
        int collectionId,
        MultipleMode multipleMode,
        string count) =>
        new(
            id,
            id,
            StartType.Dynamic,
            null,
            null,
            CollectionType.Collection,
            MakeCollection(collectionId),
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            PlaybackOrder.Shuffle,
            MarathonGroupBy.None,
            false,
            false,
            null,
            FillWithGroupMode.None,
            multipleMode,
            count,
            null,
            GuideMode.Normal,
            null,
            null,
            null,
            null,
            null,
            [],
            [],
            null,
            null,
            null,
            null);

    private static ProgramScheduleItemDurationViewModel MakeDurationItem(int id, TimeSpan playoutDuration) =>
        new(
            id,
            id,
            StartType.Dynamic,
            null,
            null,
            CollectionType.Collection,
            MakeCollection(10),
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            PlaybackOrder.Shuffle,
            MarathonGroupBy.None,
            false,
            false,
            null,
            FillWithGroupMode.None,
            playoutDuration,
            TailMode.None,
            0,
            null,
            GuideMode.Normal,
            null,
            null,
            null,
            null,
            null,
            [],
            [],
            null,
            null,
            null,
            null);

    private static ProgramScheduleItemFloodViewModel MakeFloodItem(int id, int collectionId) =>
        new(
            id,
            id,
            StartType.Dynamic,
            null,
            null,
            CollectionType.Collection,
            MakeCollection(collectionId),
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            PlaybackOrder.Shuffle,
            MarathonGroupBy.None,
            false,
            false,
            null,
            FillWithGroupMode.None,
            null,
            GuideMode.Normal,
            null,
            null,
            null,
            null,
            null,
            [],
            [],
            null,
            null,
            null,
            null);

    private static MediaCollectionViewModel MakeCollection(int id) =>
        new(CollectionType.Collection, id, $"Collection {id}", false, MediaItemState.Normal);

    private static Movie MakeMovie(int minutes) =>
        new()
        {
            MediaVersions = [new MediaVersion { Duration = TimeSpan.FromMinutes(minutes) }]
        };

    private static RemoteStream MakeRemoteStreamWithFallbackDuration(int minutes) =>
        new()
        {
            Duration = TimeSpan.FromMinutes(minutes),
            MediaVersions = [new MediaVersion { Duration = TimeSpan.Zero }]
        };
}
