using TeleWave.Domain.Programming;
using Xunit;

namespace TeleWave.Domain.Tests.Programming;

/// <summary>
/// Шаблон стыка: порядок врезок и развилки. Главный инвариант — врезки одной развилки лежат
/// непрерывным отрезком: развилка занимает одно место в цепочке, и разбросанные участники
/// не имели бы смысла.
/// </summary>
public class JunctionTemplateTests
{
    private static JunctionElementSettings Settings(
        JunctionElementKind kind = JunctionElementKind.Ad,
        Guid? groupId = null,
        Guid? bumperTemplateId = null,
        Guid? bumperVariantId = null,
        JunctionAmountMode amountMode = JunctionAmountMode.Count,
        int amountValue = 1,
        bool isRequired = false,
        string? choiceKey = null,
        int choiceWeight = 1,
        string? conditionsJson = null
    ) =>
        new(
            kind,
            groupId,
            bumperTemplateId,
            bumperVariantId,
            amountMode,
            amountValue,
            isRequired,
            choiceKey,
            choiceWeight,
            conditionsJson
        );

    [Fact]
    public void Create_TrimsName_AndStartsEmpty()
    {
        var junction = JunctionTemplate.Create("  Прайм  ");

        Assert.Equal("Прайм", junction.Name);
        Assert.Null(junction.MaxTotalSeconds);
        Assert.Empty(junction.Elements);
        Assert.NotEqual(Guid.Empty, junction.Id);
    }

    [Fact]
    public void Update_DropsNonPositiveCap()
    {
        var junction = JunctionTemplate.Create("Прайм");

        junction.Update(" Ночь ", 120);
        Assert.Equal("Ночь", junction.Name);
        Assert.Equal(120, junction.MaxTotalSeconds);

        junction.Update("Ночь", 0);
        Assert.Null(junction.MaxTotalSeconds);
    }

    [Fact]
    public void AddElement_AppendsWithNextPosition()
    {
        var junction = JunctionTemplate.Create("Прайм");

        var first = junction.AddElement(JunctionElementKind.Ad);
        var second = junction.AddElement(JunctionElementKind.Bumper);

        Assert.Equal(0, first.Position);
        Assert.Equal(1, second.Position);
        Assert.Equal(second, junction.FindElement(second.Id));
        Assert.Null(junction.FindElement(Guid.NewGuid()));
    }

    [Fact]
    public void RemoveElement_RenumbersRest()
    {
        var junction = JunctionTemplate.Create("Прайм");
        var first = junction.AddElement(JunctionElementKind.Ad);
        var second = junction.AddElement(JunctionElementKind.Promo);
        var third = junction.AddElement(JunctionElementKind.Filler);

        Assert.True(junction.RemoveElement(first.Id));
        Assert.False(junction.RemoveElement(Guid.NewGuid()));

        Assert.Equal(0, second.Position);
        Assert.Equal(1, third.Position);
    }

    [Fact]
    public void Reorder_PutsRequestedFirst_AndKeepsRestBehind()
    {
        var junction = JunctionTemplate.Create("Прайм");
        var first = junction.AddElement(JunctionElementKind.Ad);
        var second = junction.AddElement(JunctionElementKind.Promo);
        var third = junction.AddElement(JunctionElementKind.Filler);

        junction.Reorder([third.Id, first.Id]);

        Assert.Equal(0, third.Position);
        Assert.Equal(1, first.Position);
        // Не упомянутые остаются позади, сохраняя относительный порядок.
        Assert.Equal(2, second.Position);
    }

    [Fact]
    public void Reorder_PullsForkMembersTogether()
    {
        var junction = JunctionTemplate.Create("Прайм");
        var ad = junction.AddElement(JunctionElementKind.Ad);
        var promo = junction.AddElement(JunctionElementKind.Promo);
        var filler = junction.AddElement(JunctionElementKind.Filler);
        ad.SetChoice("fork", 1);
        filler.SetChoice("fork", 3);

        junction.Reorder([ad.Id, promo.Id, filler.Id]);

        // Развилка приезжает к первому упоминанию метки — между ними чужой врезке места нет.
        Assert.Equal(0, ad.Position);
        Assert.Equal(1, filler.Position);
        Assert.Equal(2, promo.Position);
    }

    [Fact]
    public void Reorder_MovesWholeFork_ByAnyMember()
    {
        var junction = JunctionTemplate.Create("Прайм");
        var bumper = junction.AddElement(JunctionElementKind.Bumper);
        var ad = junction.AddElement(JunctionElementKind.Ad);
        var promo = junction.AddElement(JunctionElementKind.Promo);
        ad.SetChoice("fork", 1);
        promo.SetChoice("fork", 1);

        // Тащим за второго участника — развилка переезжает целиком.
        junction.Reorder([promo.Id, bumper.Id]);

        Assert.Equal(0, promo.Position);
        Assert.Equal(1, ad.Position);
        Assert.Equal(2, bumper.Position);
    }

    [Fact]
    public void ElementUpdate_KeepsOnlySourceOfItsKind()
    {
        var junction = JunctionTemplate.Create("Прайм");
        var element = junction.AddElement(JunctionElementKind.Ad);
        var group = Guid.NewGuid();
        var template = Guid.NewGuid();
        var variant = Guid.NewGuid();

        element.Update(Settings(groupId: group, bumperTemplateId: template, amountValue: 4));
        Assert.Equal(group, element.GroupId);
        // Ссылка от чужого типа потом читалась бы генератором как настройка.
        Assert.Null(element.BumperTemplateId);
        Assert.Equal(4, element.AmountValue);

        element.Update(
            Settings(
                kind: JunctionElementKind.Bumper,
                groupId: group,
                bumperTemplateId: template,
                bumperVariantId: variant
            )
        );
        Assert.Null(element.GroupId);
        Assert.Equal(template, element.BumperTemplateId);
        Assert.Equal(variant, element.BumperVariantId);
    }

    [Fact]
    public void ElementUpdate_ClampsValues_AndBlankStringsBecomeNull()
    {
        var junction = JunctionTemplate.Create("Прайм");
        var element = junction.AddElement(JunctionElementKind.Ad);

        element.Update(
            Settings(
                groupId: Guid.NewGuid(),
                amountValue: 0,
                choiceKey: "   ",
                choiceWeight: -5,
                conditionsJson: "  "
            )
        );

        Assert.Equal(1, element.AmountValue);
        Assert.Null(element.ChoiceKey);
        Assert.Equal(0, element.ChoiceWeight);
        Assert.Null(element.ConditionsJson);
    }

    [Fact]
    public void SetChoice_TrimsKey_AndClampsWeight()
    {
        var junction = JunctionTemplate.Create("Прайм");
        var element = junction.AddElement(JunctionElementKind.Ad);

        element.SetChoice("  fork  ", -2);
        Assert.Equal("fork", element.ChoiceKey);
        Assert.Equal(0, element.ChoiceWeight);

        element.SetChoice(null, 3);
        Assert.Null(element.ChoiceKey);
        Assert.Equal(3, element.ChoiceWeight);
    }
}
