using TeleWave.Domain.Programming;
using Xunit;

namespace TeleWave.Domain.Tests.Programming;

public class GroupTests
{
    private static IReadOnlyList<Guid> Order(Group group) =>
        group.Items.OrderBy(i => i.Position).Select(i => i.Id).ToList();

    [Fact]
    public void AddElement_AppendsAndRejectsDuplicates()
    {
        var group = Group.Create("Боевики 90-х");
        var showId = Guid.NewGuid();

        Assert.NotNull(group.AddElement(GroupElementKind.Show, showId));
        Assert.Null(group.AddElement(GroupElementKind.Show, showId));
        Assert.Single(group.Items);
    }

    [Fact]
    public void AddElement_SameIdDifferentKind_AreDistinct()
    {
        var group = Group.Create("Смешанная");
        var id = Guid.NewGuid();

        group.AddElement(GroupElementKind.Show, id);
        group.AddElement(GroupElementKind.Collection, id);

        Assert.Equal(2, group.Items.Count);
    }

    [Fact]
    public void RemoveElement_DropsAllMatchingPositions()
    {
        var group = Group.Create("Боевики");
        var showId = Guid.NewGuid();
        group.AddElement(GroupElementKind.Show, showId);
        group.AddElement(GroupElementKind.Show, Guid.NewGuid());

        var removed = group.RemoveElement(GroupElementKind.Show, showId);

        Assert.Equal(1, removed);
        Assert.Single(group.Items);
    }

    [Fact]
    public void SetWeight_ClampsNegativeToZero()
    {
        var group = Group.Create("Боевики");
        var item = group.AddElement(GroupElementKind.Show, Guid.NewGuid())!;

        group.SetWeight(item.Id, -5);

        // Ноль — законный вес («исключить, не удаляя»), отрицательный сломал бы взвешенный выбор.
        Assert.Equal(0, item.Weight);
    }

    [Fact]
    public void SetWeight_UnknownItem_ReturnsFalse()
    {
        var group = Group.Create("Боевики");

        Assert.False(group.SetWeight(Guid.NewGuid(), 3));
    }

    [Fact]
    public void Reorder_KeepsUnmentionedAfterRequested()
    {
        var group = Group.Create("Боевики");
        var a = group.AddElement(GroupElementKind.Show, Guid.NewGuid())!;
        var b = group.AddElement(GroupElementKind.Show, Guid.NewGuid())!;
        var c = group.AddElement(GroupElementKind.Show, Guid.NewGuid())!;

        group.Reorder([c.Id]);

        Assert.Equal([c.Id, a.Id, b.Id], Order(group));
    }

    [Fact]
    public void SetFilter_TreatsBlankAsAbsent()
    {
        var group = Group.Create("Боевики");

        group.SetFilter("   ");

        Assert.Null(group.FilterJson);
    }

    [Fact]
    public void UpdateStats_StoresSnapshot()
    {
        var group = Group.Create("Боевики");
        var at = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);

        group.UpdateStats(3, 42, TimeSpan.FromHours(18), at);

        Assert.Equal(3, group.ItemCount);
        Assert.Equal(42, group.UnitCount);
        Assert.Equal(TimeSpan.FromHours(18), group.TotalDuration);
        Assert.Equal(at, group.StatsComputedAt);
    }

    [Fact]
    public void SetMode_ToDynamic_KeepsItemsAsPinned()
    {
        // Смена режима не должна вымывать из эфира то, что уже стояло в слотах.
        var group = Group.Create("Боевики");
        var show = Guid.NewGuid();
        group.AddElement(GroupElementKind.Show, show);

        group.SetMode(GroupMode.Dynamic);

        Assert.Equal(GroupMode.Dynamic, group.Mode);
        Assert.Equal(GroupItemRole.Pinned, group.Items.Single().Role);
    }

    [Fact]
    public void SetMode_BackToStatic_DropsExclusionsAndUnpins()
    {
        var group = Group.Create("Боевики");
        group.SetMode(GroupMode.Dynamic);
        group.AddElement(GroupElementKind.Show, Guid.NewGuid());
        group.Exclude(GroupElementKind.Show, Guid.NewGuid());

        group.SetMode(GroupMode.Static);

        // Исключать в статической группе нечего: состав и есть список.
        var item = Assert.Single(group.Items);
        Assert.Equal(GroupItemRole.Member, item.Role);
    }

    [Fact]
    public void SetMode_SameMode_ChangesNothing()
    {
        var group = Group.Create("Боевики");
        group.AddElement(GroupElementKind.Show, Guid.NewGuid());

        group.SetMode(GroupMode.Static);

        Assert.Equal(GroupItemRole.Member, group.Items.Single().Role);
    }

    [Fact]
    public void AddElement_InDynamicGroup_PinsByDefault()
    {
        // Иначе правило вымело бы позицию на первом же обращении.
        var group = Group.Create("Боевики");
        group.SetMode(GroupMode.Dynamic);

        var item = group.AddElement(GroupElementKind.Show, Guid.NewGuid());

        Assert.Equal(GroupItemRole.Pinned, item!.Role);
    }

    [Fact]
    public void Exclude_TurnsPinnedIntoExclusion()
    {
        // Держать на один элемент обе противоположные поправки нельзя.
        var group = Group.Create("Боевики");
        group.SetMode(GroupMode.Dynamic);
        var show = Guid.NewGuid();
        group.AddElement(GroupElementKind.Show, show);

        Assert.True(group.Exclude(GroupElementKind.Show, show));

        Assert.Equal(GroupItemRole.Excluded, group.Items.Single().Role);
    }

    [Fact]
    public void Exclude_InStaticGroup_IsRefused()
    {
        var group = Group.Create("Боевики");

        Assert.False(group.Exclude(GroupElementKind.Show, Guid.NewGuid()));
        Assert.Empty(group.Items);
    }

    [Fact]
    public void RemoveExclusion_ReturnsElementToTheRule()
    {
        var group = Group.Create("Боевики");
        group.SetMode(GroupMode.Dynamic);
        var show = Guid.NewGuid();
        group.Exclude(GroupElementKind.Show, show);

        Assert.True(group.RemoveExclusion(GroupElementKind.Show, show));
        Assert.Empty(group.Items);
        // Снимать нечего — второй раз это уже не поправка.
        Assert.False(group.RemoveExclusion(GroupElementKind.Show, show));
    }
}
