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

namespace ErsatzTV.Core.Tests.Scheduling;

public class MultiPartEpisodeGrouperTests
{
    [Test]
    [TestCase("Episode 1", "Episode 2 (1)", "Episode 3 (2)", "Episode 4")]
    [TestCase("Episode 1 - More", "Episode 2 (1) - Title", "Episode 3 (2) - After", "Episode 4 - Dash")]
    [TestCase("Episode 1", "Episode 2 Part 1", "Episode 3 Part 2", "Episode 4")]
    [TestCase("Episode 1", "Episode 2 (Part 1)", "Episode 3 (Part 2)", "Episode 4")]
    [TestCase("Episode 1", "Episode 2 (Part One)", "Episode 3 (Part Two)", "Episode 4")]
    public void NotGrouped_Grouped_NotGrouped(string one, string two, string three, string four)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 2),
            NamedEpisode(three, 1, 1, 3),
            NamedEpisode(four, 1, 1, 4)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(3);
        ShouldHaveOneItem(result, mediaItems[0]);
        ShouldHaveTwoItems(result, mediaItems[1], mediaItems[2]);
        ShouldHaveOneItem(result, mediaItems[3]);
    }

    [Test]
    [TestCase("Episode 1 (1)", "Episode 2 - Part 2", "Episode 3")]
    [TestCase("Episode 1 Part 1", "Episode 2 (2) - More", "Episode 3 - After")]
    [TestCase("Episode 1 Part 1", "Episode 2 (II)", "Episode 3")]
    [TestCase("Episode 1 Part One", "Episode 2 (II)", "Episode 3")]
    [TestCase("Episode 1 (Part One)", "Episode 2 (II)", "Episode 3")]
    [TestCase("Episode 1 (1)", "Episode 2 (Part 2)", "Episode 3")]
    public void MixedNaming_Group(string one, string two, string three)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 2),
            NamedEpisode(three, 1, 1, 3)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(2);
        ShouldHaveTwoItems(result, mediaItems[0], mediaItems[1]);
        ShouldHaveOneItem(result, mediaItems[2]);
    }

    [Test]
    [TestCase("The Meddlers (Part One)", "The Meddlers (Part Two)", "The Meddlers (Part Three)")]
    [TestCase("The Slaves of Jedikiah, Part 1", "The Slaves of Jedikiah, Part 2", "The Slaves of Jedikiah, Part 3")]
    [TestCase(
        "An Unearthly Child: An Unearthly Child (1)",
        "An Unearthly Child: The Cave of Skulls (2)",
        "An Unearthly Child: The Forest of Fear (3)")]
    [TestCase("The Savages (1)", "The Savages (2)", "The Savages (3)")]
    public void All_Grouped(string one, string two, string three)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 2),
            NamedEpisode(three, 1, 1, 3)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(1);
        ShouldHaveMultipleItems(result, mediaItems[0], [mediaItems[1], mediaItems[2]]);
    }

    [Test]
    [TestCase("Episode 1 (5)", "Episode 2 - (6)", "Episode 3")]
    [TestCase("Episode 1 Part 5", "Episode 2 Part 6", "Episode 3 - After")]
    [TestCase("Episode 1 Part (V)", "Episode 2 (VI)", "Episode 3")]
    [TestCase("Episode 1 (Part 5)", "Episode 2 (Part 6)", "Episode 3")]
    public void Only_Later_Parts(string one, string two, string three)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 2),
            NamedEpisode(three, 1, 1, 3)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(2);
        ShouldHaveTwoItems(result, mediaItems[0], mediaItems[1]);
        ShouldHaveOneItem(result, mediaItems[2]);
    }

    [Test]
    [TestCase("Episode 1 (1)", "Episode 2 (2)", "Episode 3")]
    [TestCase("Episode 1 (1) - More", "Episode 2 (2) - Title", "Episode 3 - After")]
    [TestCase("Episode 1 Part 1", "Episode 2 Part 2", "Episode 3")]
    [TestCase("Episode 1 (Part 1)", "Episode 2 (Part 2)", "Episode 3")]
    public void Grouped_NotGrouped(string one, string two, string three)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 2),
            NamedEpisode(three, 1, 1, 3)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(2);
        ShouldHaveTwoItems(result, mediaItems[0], mediaItems[1]);
        ShouldHaveOneItem(result, mediaItems[2]);
    }

    [Test]
    [TestCase("Episode 1 (1)", "Episode 2 (2)", "Episode 3", "Episode 4 (1)", "Episode 5 (2)")]
    [TestCase(
        "Episode 1 (1) - More",
        "Episode 2 (2) - Title",
        "Episode 3 - After",
        "Episode 4 (1) - Dash",
        "Episode 5 (2) - Again")]
    [TestCase("Episode 1 Part 1", "Episode 2 Part 2", "Episode 3", "Episode 4 Part 1", "Episode 5 Part 2")]
    [TestCase("Episode 1 (Part 1)", "Episode 2 (Part 2)", "Episode 3", "Episode 4 (Part 1)", "Episode 5 (Part 2)")]
    public void Grouped_NotGrouped_Grouped(string one, string two, string three, string four, string five)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 2),
            NamedEpisode(three, 1, 1, 3),
            NamedEpisode(four, 1, 1, 4),
            NamedEpisode(five, 1, 1, 5)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(3);
        ShouldHaveTwoItems(result, mediaItems[0], mediaItems[1]);
        ShouldHaveOneItem(result, mediaItems[2]);
        ShouldHaveTwoItems(result, mediaItems[3], mediaItems[4]);
    }

    [Test]
    [TestCase("Episode 1 (1)", "Episode 2 (2)", "Episode 3 (1)", "Episode 4 (2)")]
    [TestCase("Episode 1 (1) - More", "Episode 2 (2) - Title", "Episode 3 (1) - After", "Episode 4 (2) - Dash")]
    [TestCase("Episode 1 Part 1", "Episode 2 Part 2", "Episode 3 Part 1", "Episode 4 Part 2")]
    [TestCase("Episode 1 (Part 1)", "Episode 2 (Part 2)", "Episode 3 (Part 1)", "Episode 4 (Part 2)")]
    public void Grouped_Grouped(string one, string two, string three, string four)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 2),
            NamedEpisode(three, 1, 1, 3),
            NamedEpisode(four, 1, 1, 4)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(2);
        ShouldHaveTwoItems(result, mediaItems[0], mediaItems[1]);
        ShouldHaveTwoItems(result, mediaItems[2], mediaItems[3]);
    }

    [Test]
    [TestCase("Episode 1", "Episode 2 (2)", "Episode 3 (1)", "Episode 4 (2)")]
    [TestCase("Episode 1 - More", "Episode 2 (2) - Title", "Episode 3 (1) - After", "Episode 4 (2) - Dash")]
    [TestCase("Episode 1", "Episode 2 Part 2", "Episode 3 Part 1", "Episode 4 Part 2")]
    [TestCase("Episode 1", "Episode 2 (Part 2)", "Episode 3 (Part 1)", "Episode 4 (Part 2)")]
    public void Part2_Without_Part1(string one, string two, string three, string four)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 2),
            NamedEpisode(three, 1, 1, 3),
            NamedEpisode(four, 1, 1, 4)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(3);
        ShouldHaveOneItem(result, mediaItems[0]);
        ShouldHaveOneItem(result, mediaItems[1]);
        ShouldHaveTwoItems(result, mediaItems[2], mediaItems[3]);
    }

    [Test]
    [TestCase("Episode 1", "Episode 2 (2)", "Episode 3 (3)", "Episode 4")]
    [TestCase("Episode 1 - More", "Episode 2 (2) - Title", "Episode 3 (3) - After", "Episode 4 - Dash")]
    [TestCase("Episode 1", "Episode 2 Part 2", "Episode 3 Part 3", "Episode 4")]
    [TestCase("Episode 1", "Episode 2 (Part 2)", "Episode 3 (Part 3)", "Episode 4")]
    public void Part2And3_Without_Part1(string one, string two, string three, string four)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 2),
            NamedEpisode(three, 1, 1, 3),
            NamedEpisode(four, 1, 1, 4)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(3);
        ShouldHaveOneItem(result, mediaItems[0]);
        ShouldHaveTwoItems(result, mediaItems[1], mediaItems[2]);
        ShouldHaveOneItem(result, mediaItems[3]);
    }

    [Test]
    [TestCase("Episode 1 (1)", "Episode 3 (3)", "Episode 4", "Episode 5")]
    [TestCase("Episode 1 (1) - More", "Episode 3 (3) - Title", "Episode 4 - After", "Episode 5 - Dash")]
    [TestCase("Episode 1 Part 1", "Episode 3 Part 3", "Episode 4", "Episode 5")]
    [TestCase("Episode 1 (Part 1)", "Episode 3 (Part 3)", "Episode 4", "Episode 5")]
    public void Skip_Part(string one, string two, string three, string four)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 3),
            NamedEpisode(three, 1, 1, 4),
            NamedEpisode(four, 1, 1, 5)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(4);
        ShouldHaveOneItem(result, mediaItems[0]);
        ShouldHaveOneItem(result, mediaItems[1]);
        ShouldHaveOneItem(result, mediaItems[2]);
        ShouldHaveOneItem(result, mediaItems[3]);
    }

    [Test]
    [TestCase("Episode 1 (1)", "Episode 3 (1)", "Episode 4 (2)", "Episode 5")]
    [TestCase("Episode 1 (1) - More", "Episode 3 (1) - Title", "Episode 4 (2) - After", "Episode 5 - Dash")]
    [TestCase("Episode 1 Part 1", "Episode 3 Part 1", "Episode 4 Part 2", "Episode 5")]
    [TestCase("Episode 1 (Part 1)", "Episode 3 (Part 1)", "Episode 4 (Part 2)", "Episode 5")]
    public void Repeat_Part(string one, string two, string three, string four)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1),
            NamedEpisode(two, 1, 1, 3),
            NamedEpisode(three, 1, 1, 4),
            NamedEpisode(four, 1, 1, 5)
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(3);
        ShouldHaveOneItem(result, mediaItems[0]);
        ShouldHaveTwoItems(result, mediaItems[1], mediaItems[2]);
        ShouldHaveOneItem(result, mediaItems[3]);
    }

    [Test]
    [TestCase("S1 Episode 1 (1)", "S2 Episode 3 (1)", "S1 Episode 2 (2)", "S1 Episode 5")]
    [TestCase(
        "S1 Episode 1 (1) - More",
        "S2 Episode 3 (1) - Title",
        "S1 Episode 2 (2) - After",
        "S1 Episode 5 - Dash")]
    [TestCase("S1 Episode 1 Part 1", "S2 Episode 3 Part 1", "S1 Episode 2 Part 2", "S1 Episode 5")]
    [TestCase("S1 Episode 1 (Part 1)", "S2 Episode 3 (Part 1)", "S1 Episode 2 (Part 2)", "S1 Episode 5")]
    public void Mixed_Shows_Chronologically(string one, string two, string three, string four)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1, new DateTime(2020, 1, 1)),
            NamedEpisode(two, 2, 1, 3, new DateTime(2020, 1, 2)),
            NamedEpisode(three, 1, 1, 2, new DateTime(2020, 1, 3)),
            NamedEpisode(four, 1, 1, 5, new DateTime(2020, 1, 4))
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, false);

        result.Count.ShouldBe(3);
        ShouldHaveTwoItems(result, mediaItems[0], mediaItems[2]);
        ShouldHaveOneItem(result, mediaItems[1]);
        ShouldHaveOneItem(result, mediaItems[3]);
    }

    [Test]
    [TestCase("S1 Episode 1 (1)", "S2 Episode 3 (2)", "S1 Episode 2 (3)", "S1 Episode 5")]
    [TestCase(
        "S1 Episode 1 (1) - More",
        "S2 Episode 3 (2) - Title",
        "S1 Episode 2 (3) - After",
        "S1 Episode 5 - Dash")]
    [TestCase("S1 Episode 1 Part 1", "S2 Episode 3 Part 2", "S1 Episode 2 Part 3", "S1 Episode 5")]
    [TestCase("S1 Episode 1 (Part 1)", "S2 Episode 3 (Part 2)", "S1 Episode 2 (Part 3)", "S1 Episode 5")]
    public void Mixed_Shows_Chronologically_Crossover(string one, string two, string three, string four)
    {
        var mediaItems = new List<MediaItem>
        {
            NamedEpisode(one, 1, 1, 1, new DateTime(2020, 1, 1)),
            NamedEpisode(two, 2, 1, 3, new DateTime(2020, 1, 2)),
            NamedEpisode(three, 1, 1, 2, new DateTime(2020, 1, 3)),
            NamedEpisode(four, 1, 1, 5, new DateTime(2020, 1, 4))
        };

        List<GroupedMediaItem> result = MultiPartEpisodeGrouper.GroupMediaItems(mediaItems, true);

        result.Count.ShouldBe(2);
        ShouldHaveMultipleItems(result, mediaItems[0], new List<MediaItem> { mediaItems[1], mediaItems[2] });
        ShouldHaveOneItem(result, mediaItems[3]);
    }

    private static Episode NamedEpisode(
        string title,
        int showId,
        int season,
        int episode,
        DateTime? releaseDate = null) =>
        new()
        {
            EpisodeMetadata = new List<EpisodeMetadata>
            {
                new() { Title = title, ReleaseDate = releaseDate, EpisodeNumber = episode }
            },
            Season = new Season
            {
                SeasonNumber = season,
                Show = new Show { Id = showId },
                ShowId = showId
            }
        };

    private static void ShouldHaveOneItem(IEnumerable<GroupedMediaItem> result, MediaItem item) =>
        result.Filter(g => g.First == item && Optional(g.Additional).Flatten().HeadOrNone() == None)
            .Count().ShouldBe(1);

    private static void ShouldHaveTwoItems(
        IEnumerable<GroupedMediaItem> result,
        MediaItem first,
        MediaItem additional) =>
        result.Filter(g => g.First == first && Optional(g.Additional).Flatten().HeadOrNone() == Some(additional))
            .Count().ShouldBe(1);

    private static void ShouldHaveMultipleItems(
        IEnumerable<GroupedMediaItem> result,
        MediaItem first,
        List<MediaItem> additional) =>
        result.Filter(g => g.First == first && g.Additional != null && g.Additional.Count == additional.Count &&
                           additional.ForAll(g.Additional.Contains))
            .Count().ShouldBe(1);
}
