using TeleWave.Application.Programming.Templates;
using TeleWave.Domain.Library;
using Xunit;

namespace TeleWave.Application.Tests.Programming;

/// <summary>Окна детского времени: разрешение по времени суток, включая переход через полночь.</summary>
public class PlanningRulesTests
{
    [Theory]
    [InlineData(7, 0, true)]
    [InlineData(22, 59, true)]
    [InlineData(6, 0, true)] // ровно начало
    [InlineData(23, 0, false)] // ровно конец — уже вне окна
    [InlineData(2, 0, false)]
    public void AudienceAt_DayWindow(int hour, int minute, bool inside)
    {
        var rules = new PlanningRules([
            new AudienceWindow(new TimeOnly(6, 0), new TimeOnly(23, 0), ShowAudience.Pg13),
        ]);

        var result = rules.AudienceAt(new TimeOnly(hour, minute));

        Assert.Equal(inside ? ShowAudience.Pg13 : null, result);
    }

    [Theory]
    [InlineData(23, 30, true)]
    [InlineData(3, 0, true)]
    [InlineData(12, 0, false)]
    public void AudienceAt_WindowCrossingMidnight(int hour, int minute, bool inside)
    {
        // «С 23:00 до 06:00» — ночное окно, границы сравниваются в обратную сторону.
        var rules = new PlanningRules([
            new AudienceWindow(new TimeOnly(23, 0), new TimeOnly(6, 0), ShowAudience.Nc17),
        ]);

        var result = rules.AudienceAt(new TimeOnly(hour, minute));

        Assert.Equal(inside ? ShowAudience.Nc17 : null, result);
    }

    [Fact]
    public void AudienceAt_OverlappingWindows_TakesTheStrictest()
    {
        // Широкое окно, случайно наложенное поверх детского, не должно его отменять.
        var rules = new PlanningRules([
            new AudienceWindow(new TimeOnly(6, 0), new TimeOnly(23, 0), ShowAudience.R),
            new AudienceWindow(new TimeOnly(7, 0), new TimeOnly(10, 0), ShowAudience.G),
        ]);

        Assert.Equal(ShowAudience.G, rules.AudienceAt(new TimeOnly(8, 0)));
        Assert.Equal(ShowAudience.R, rules.AudienceAt(new TimeOnly(12, 0)));
    }

    [Fact]
    public void AudienceAt_WithoutWindows_IsUnlimited()
    {
        Assert.Null(new PlanningRules().AudienceAt(new TimeOnly(3, 0)));
    }

    [Fact]
    public void Json_RoundTrips()
    {
        var rules = new PlanningRules(
            [new AudienceWindow(new TimeOnly(6, 0), new TimeOnly(23, 0), ShowAudience.Pg13)],
            new RepeatLimitRule(7, 2),
            MaxBreakMinutesPerHour: 12,
            MaxGenreSharePercent: 40,
            MaxFallbackSharePercent: 15
        );

        var restored = PlanningRules.FromJson(rules.ToJson());

        // Сравниваем по полям: у record со списком внутри равенство ссылочное, и `Assert.Equal`
        // на самих правилах проверял бы не то.
        Assert.NotNull(restored);
        Assert.Equal(rules.MaxAudienceByTime!, restored.MaxAudienceByTime!);
        Assert.Equal(rules.MaxRepeatsInWindow, restored.MaxRepeatsInWindow);
        Assert.Equal(rules.MaxBreakMinutesPerHour, restored.MaxBreakMinutesPerHour);
        Assert.Equal(rules.MaxGenreSharePercent, restored.MaxGenreSharePercent);
        Assert.Equal(rules.MaxFallbackSharePercent, restored.MaxFallbackSharePercent);
    }

    [Fact]
    public void Json_WritesMpaaSpelling()
    {
        // Рейтинг уезжает в jsonb шаблона и в API ровно тем написанием, каким приходит от источников.
        // Round-trip этого не поймает: он одинаково зелёный и на «Pg13», а такое значение потом
        // придётся переводить на каждой границе.
        var json = new PlanningRules([
            new AudienceWindow(new TimeOnly(6, 0), new TimeOnly(23, 0), ShowAudience.Pg13),
        ]).ToJson();

        Assert.Contains("\"PG-13\"", json, StringComparison.Ordinal);
    }

    [Fact]
    public void FromJson_Garbage_IsNull()
    {
        // Битый JSON не должен ронять генерацию: правило просто считается незаданным.
        Assert.Null(PlanningRules.FromJson("{"));
        Assert.Null(PlanningRules.FromJson(null));
    }
}
