using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Core.Scheduling.BlockScheduling;
using NUnit.Framework;
using Shouldly;

namespace ErsatzTV.Core.Tests.Scheduling;

// Direct unit coverage for the enumerator-construction helper extracted from the Scripted
// (SchedulingEngine.EnumeratorForContent) and Sequential/YAML (EnumeratorCache.GetEnumeratorForContent)
// engines (#395). These pin the two behaviors the issue flags as traps:
//   - Shuffle must build the *block* enumerator, NOT Classic's ShuffledMediaCollectionEnumerator (a
//     different algorithm keyed on the same PlaybackOrder), and
//   - every order the two engines don't support returns None, so each caller logs its own #70 warning
//     instead of silently scheduling nothing.
// The Scripted engine has no golden (its external-process/HTTP transport is integration-only, #563), so
// this direct helper test is the in-process regression net for the shared construction it drives.
[TestFixture]
public class ContentEnumeratorBuilderTests
{
    [Test]
    public void Chronological_Builds_Chronological_Enumerator()
    {
        Option<IMediaCollectionEnumerator> result = ContentEnumeratorBuilder.ForContent(
            [FakeMovie(1), FakeMovie(2)],
            new CollectionEnumeratorState(),
            PlaybackOrder.Chronological,
            multiPart: false);

        result.IsSome.ShouldBeTrue();
        foreach (IMediaCollectionEnumerator enumerator in result)
        {
            enumerator.ShouldBeOfType<ChronologicalMediaCollectionEnumerator>();
        }
    }

    [Test]
    public void Shuffle_Builds_Block_Shuffle_Enumerator_Not_Classic()
    {
        Option<IMediaCollectionEnumerator> result = ContentEnumeratorBuilder.ForContent(
            [FakeMovie(1), FakeMovie(2)],
            new CollectionEnumeratorState(),
            PlaybackOrder.Shuffle,
            multiPart: false);

        result.IsSome.ShouldBeTrue();
        foreach (IMediaCollectionEnumerator enumerator in result)
        {
            // The documented trap: Shuffle here is the block algorithm, never Classic's shuffle.
            enumerator.ShouldBeOfType<BlockPlayoutShuffledMediaCollectionEnumerator>();
            enumerator.ShouldNotBeOfType<ShuffledMediaCollectionEnumerator>();
        }
    }

    [Test]
    public void Shuffle_MultiPart_Also_Builds_Block_Shuffle_Enumerator()
    {
        // "(1)"/"(2)" are a two-part episode MultiPartEpisodeGrouper keeps together; multiPart routes the
        // items through it before the block enumerator. Grouping mechanics are covered by
        // MultiPartEpisodeGrouper's / ShuffleSourceBuilder's own tests; here we pin that the flag path still
        // produces the block enumerator (and doesn't throw on the grouped list).
        List<MediaItem> parts =
        [
            NamedEpisode("Episode 1 (1)", 1),
            NamedEpisode("Episode 2 (2)", 2)
        ];

        Option<IMediaCollectionEnumerator> result = ContentEnumeratorBuilder.ForContent(
            parts,
            new CollectionEnumeratorState(),
            PlaybackOrder.Shuffle,
            multiPart: true);

        result.IsSome.ShouldBeTrue();
        foreach (IMediaCollectionEnumerator enumerator in result)
        {
            enumerator.ShouldBeOfType<BlockPlayoutShuffledMediaCollectionEnumerator>();
        }
    }

    [TestCase(PlaybackOrder.None)]
    [TestCase(PlaybackOrder.Random)]
    [TestCase(PlaybackOrder.ShuffleInOrder)]
    [TestCase(PlaybackOrder.MultiEpisodeShuffle)]
    [TestCase(PlaybackOrder.SeasonEpisode)]
    [TestCase(PlaybackOrder.RandomRotation)]
    [TestCase(PlaybackOrder.Marathon)]
    [TestCase(PlaybackOrder.WeightedShuffle)]
    public void Unsupported_Order_Returns_None(PlaybackOrder order)
    {
        // #70: these two engines support only Chronological + Shuffle; every other order returns None so the
        // caller logs a "not supported" warning instead of silently scheduling nothing.
        Option<IMediaCollectionEnumerator> result = ContentEnumeratorBuilder.ForContent(
            [FakeMovie(1)],
            new CollectionEnumeratorState(),
            order,
            multiPart: false);

        result.IsNone.ShouldBeTrue();
    }

    private static Episode NamedEpisode(string title, int id) => new()
    {
        Id = id,
        EpisodeMetadata = [new EpisodeMetadata { Title = title, EpisodeNumber = id }],
        Season = new Season { SeasonNumber = 1, Show = new Show { Id = 1 }, ShowId = 1 }
    };

    private static Movie FakeMovie(int id) => new()
    {
        Id = id,
        MediaVersions = [],
        MovieMetadata =
        [
            new MovieMetadata { ReleaseDate = new DateTime(2020, 1, id) }
        ]
    };
}
