using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Domain.Broadcast;
using Xunit;

namespace TeleWave.Application.Tests.Broadcast;

/// <summary>
/// Подстановка плейсхолдеров. Правило одно и важное: неизвестное значение схлопывается вместе
/// с осиротевшим разделителем, а не оставляет дыру в кадре.
/// </summary>
public class BumperPlaceholdersTests
{
    private static readonly DateTimeOffset Moment = new(
        2026,
        4,
        6,
        21,
        24,
        0,
        TimeSpan.FromHours(3)
    );

    private static BumperContext Full() =>
        new(
            "Первый",
            4,
            Moment,
            "Симпсоны",
            "Терминатор 2",
            "с5э12",
            "с1э3",
            1991,
            "Боевик",
            new TimeOnly(21, 30),
            "Вечернее кино"
        );

    [Theory]
    [InlineData("{channel}", "Первый")]
    [InlineData("{channel.number}", "4")]
    [InlineData("{now.title}", "Симпсоны")]
    [InlineData("{next.title}", "Терминатор 2")]
    [InlineData("{now.episode}", "с5э12")]
    [InlineData("{next.episode}", "с1э3")]
    [InlineData("{next.year}", "1991")]
    [InlineData("{next.genre}", "Боевик")]
    [InlineData("{next.time}", "21:30")]
    [InlineData("{time}", "21:24")]
    [InlineData("{date}", "6 апреля")]
    [InlineData("{weekday}", "понедельник")]
    [InlineData("{slot}", "Вечернее кино")]
    public void Resolve_SubstitutesEveryToken(string text, string expected) =>
        Assert.Equal(expected, BumperPlaceholders.Resolve(text, Full()));

    [Fact]
    public void Resolve_MixesTextAndTokens()
    {
        var resolved = BumperPlaceholders.Resolve("ДАЛЕЕ В {next.time} — {next.title}", Full());

        Assert.Equal("ДАЛЕЕ В 21:30 — Терминатор 2", resolved);
    }

    [Fact]
    public void Resolve_CollapsesGapsFromMissingValues()
    {
        var empty = new BumperContext("Канал", null, Moment);

        // Ни текущей, ни следующей программы: строка писалась ради них и исчезает целиком —
        // «СЕЙЧАС» в кадре без названия ничего не значит.
        Assert.Equal("", BumperPlaceholders.Resolve("СЕЙЧАС — {now.title}", empty));
        Assert.Equal("", BumperPlaceholders.Resolve("{next.title}", empty));
        // А подставилось хоть что-то — строка остаётся, осиротевший хвост подчищается.
        Assert.Equal("Канал", BumperPlaceholders.Resolve("{channel} {channel.number}", empty));
        // Строка, где не подставился ни один плейсхолдер, исчезает целиком: «ДАЛЕЕ В» без времени —
        // это мусор в кадре, а не подпись.
        Assert.Equal("", BumperPlaceholders.Resolve("ДАЛЕЕ В {next.time}", empty));
        // А постоянный текст без плейсхолдеров остаётся всегда.
        Assert.Equal("СЕЙЧАС", BumperPlaceholders.Resolve("СЕЙЧАС", empty));
    }

    [Fact]
    public void Resolve_UnknownTokenLeavesNothing()
    {
        // Незнакомый плейсхолдер подставить нечем, а значит и строка пуста — как если бы данных
        // не нашлось. Сохранить такую строку валидатор всё равно не даст.
        Assert.Equal("", BumperPlaceholders.Resolve("Далее {next.tittle}", Full()));
        Assert.Equal("", BumperPlaceholders.Resolve("   ", Full()));
        // Постоянный текст без плейсхолдеров остаётся как есть.
        Assert.Equal("Далее", BumperPlaceholders.Resolve("Далее", Full()));
    }

    [Fact]
    public void UnknownTokens_ListsOnlyStrangers()
    {
        Assert.Equal(
            ["next.tittle", "chanel"],
            BumperPlaceholders.UnknownTokens("{next.title} {next.tittle} {chanel} {next.tittle}")
        );
        Assert.Empty(BumperPlaceholders.UnknownTokens("{channel}"));
        Assert.Empty(BumperPlaceholders.UnknownTokens(null));
    }

    [Fact]
    public void TokensIn_CollectsAcrossTexts()
    {
        var tokens = BumperPlaceholders.TokensIn(["СЕЙЧАС {now.title}", "", "{next.genre}"]);

        Assert.Equal(["next.genre", "now.title"], tokens.Order());
    }

    [Theory]
    [InlineData("{time}", true)]
    [InlineData("{date}", true)]
    [InlineData("{weekday}", true)]
    [InlineData("{next.time}", false)]
    [InlineData("СЕЙЧАС", false)]
    [InlineData(null, false)]
    public void IsVolatile_MarksTokensTiedToTheMoment(string? text, bool expected) =>
        Assert.Equal(expected, BumperPlaceholders.IsVolatile(text));

    [Theory]
    [InlineData(5, 12, "с5э12")]
    [InlineData(null, 7, "э7")]
    [InlineData(3, null, null)]
    [InlineData(null, null, null)]
    public void Episode_FormatsWhatIsKnown(int? season, int? episode, string? expected) =>
        Assert.Equal(expected, BumperPlaceholders.Episode(season, episode));

    [Fact]
    public void RenderedText_RoundTripsAndSurvivesGarbage()
    {
        var lines = new List<BumperRenderLine>
        {
            new(BumperLineStyle.Label, BumperLineColor.Accent, "СЕЙЧАС"),
            new(BumperLineStyle.Title, BumperLineColor.Text, "Симпсоны"),
        };

        var restored = BumperRenderedText.FromJson(BumperRenderedText.ToJson(lines));

        Assert.Equal(lines, restored);
        Assert.Empty(BumperRenderedText.FromJson("{не json}"));
        Assert.Empty(BumperRenderedText.FromJson(null));
    }
}
