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

namespace TeleWave.Application.Tests.Programming;

/// <summary>
/// Применимость слоёв (см. 3.4) и разрешение перекрытий: какие слоты реально действуют в сутки.
/// </summary>
public class LayerApplicabilityTests
{
    private static readonly TimeOnly DayStart = new(6, 0);

    [Fact]
    public void Empty_CoversEveryDate()
    {
        var applicability = new LayerApplicability();

        Assert.True(applicability.IsEmpty);
        Assert.True(applicability.Covers(new DateOnly(2026, 3, 17)));
    }

    [Theory]
    [InlineData(2026, 12, 25, true)] // внутри, до Нового года
    [InlineData(2026, 1, 3, true)] // внутри, после Нового года
    [InlineData(2026, 12, 20, true)] // ровно начало
    [InlineData(2026, 1, 8, true)] // ровно конец
    [InlineData(2026, 12, 19, false)] // за день до начала
    [InlineData(2026, 1, 9, false)] // на следующий день после конца
    [InlineData(2026, 6, 15, false)] // середина года
    public void AnnualRange_CrossingNewYear_IsInclusiveOnBothEnds(
        int year,
        int month,
        int day,
        bool expected
    )
    {
        // «20 декабря — 8 января» задаётся один раз и работает в любом году, поэтому сравнение идёт
        // по паре (месяц, день), а не по датам.
        var applicability = new LayerApplicability(AnnualRanges: [new AnnualRange(12, 20, 1, 8)]);

        Assert.Equal(expected, applicability.Covers(new DateOnly(year, month, day)));
    }

    [Fact]
    public void AnnualRange_WithinOneYear_DoesNotWrap()
    {
        var applicability = new LayerApplicability(AnnualRanges: [new AnnualRange(6, 1, 8, 31)]);

        Assert.True(applicability.Covers(new DateOnly(2026, 7, 4)));
        Assert.False(applicability.Covers(new DateOnly(2026, 1, 4)));
    }

    [Fact]
    public void Sections_AreCombinedWithOr()
    {
        // Понедельник ИЛИ конкретная дата: суббота из списка дат проходит, обычная суббота — нет.
        var applicability = new LayerApplicability(
            Weekdays: [1],
            SpecificDates: [new DateOnly(2026, 3, 21)]
        );

        Assert.True(applicability.Covers(new DateOnly(2026, 3, 16))); // понедельник
        Assert.True(applicability.Covers(new DateOnly(2026, 3, 21))); // суббота из списка
        Assert.False(applicability.Covers(new DateOnly(2026, 3, 14))); // другая суббота
    }

    [Fact]
    public void Build_SkipsSlotsCoveredByHigherPriorityLayer()
    {
        // Слот младшего слоя пропускается целиком, а не обрезается: половина слота означала бы
        // половину настройки — своей группы и стратегии у половинки нет.
        var template = ScheduleTemplate.Create(Guid.NewGuid(), "Сетка");
        var top = template.AddLayer("Прайм", 100);
        var bottom = template.AddLayer("Обычный", 50);

        AddSlot(top, "Кино", new TimeOnly(20, 0), 120);
        AddSlot(bottom, "Сериал", new TimeOnly(20, 30), 60);
        AddSlot(bottom, "Ночь", new TimeOnly(23, 0), 60);

        var titles = Build(template).Select(s => s.Slot.Title).Distinct().ToList();

        Assert.Contains("Кино", titles);
        Assert.Contains("Ночь", titles);
        Assert.DoesNotContain("Сериал", titles);
    }

    [Fact]
    public void Build_DropsLayersThatDoNotApplyOnTheDate()
    {
        var template = ScheduleTemplate.Create(Guid.NewGuid(), "Сетка");
        var newYear = template.AddLayer("Новогодний", 100);
        newYear.Update(
            newYear.Name,
            newYear.Priority,
            new LayerApplicability(AnnualRanges: [new AnnualRange(12, 20, 1, 8)]).ToJson(),
            isEnabled: true
        );
        AddSlot(newYear, "Ирония судьбы", new TimeOnly(20, 0), 180);

        var usual = template.AddLayer("Обычный", 50);
        AddSlot(usual, "Вечерний сериал", new TimeOnly(20, 0), 60);

        var march = Build(template, new DateTimeOffset(2026, 3, 17, 12, 0, 0, TimeSpan.Zero))
            .Select(s => s.Slot.Title)
            .ToList();
        var december = Build(template, new DateTimeOffset(2026, 12, 25, 12, 0, 0, TimeSpan.Zero))
            .Select(s => s.Slot.Title)
            .ToList();

        Assert.Contains("Вечерний сериал", march);
        Assert.DoesNotContain("Ирония судьбы", march);
        Assert.Contains("Ирония судьбы", december);
        Assert.DoesNotContain("Вечерний сериал", december);
    }

    [Fact]
    public void Build_DisabledLayerIsIgnored()
    {
        var template = ScheduleTemplate.Create(Guid.NewGuid(), "Сетка");
        var layer = template.AddLayer("Выключённый", 100);
        AddSlot(layer, "Ничего", new TimeOnly(20, 0), 60);
        layer.Update(layer.Name, layer.Priority, null, isEnabled: false);

        Assert.Empty(Build(template));
    }

    private static void AddSlot(
        GridLayer layer,
        string title,
        TimeOnly start,
        int durationMinutes
    ) => layer.AddSlot(title, start, durationMinutes);

    private static IReadOnlyList<ScheduledSlot> Build(
        ScheduleTemplate template,
        DateTimeOffset? from = null
    )
    {
        var start = from ?? new DateTimeOffset(2026, 3, 17, 12, 0, 0, TimeSpan.Zero);
        return EffectiveGridBuilder
            .Build(template, utcOffsetMinutes: 180, DayStart, start, start.AddHours(18))
            .Slots;
    }
}
