using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
using TeleWave.Application.Programming.Groups;
using TeleWave.Domain.Programming;

namespace TeleWave.Application.Programming.Templates;

/// <summary>
/// Общая часть создания и правки слота: проверки, которые нельзя доверить валидатору, потому что
/// им нужны соседние слоты и справочник групп.
/// </summary>
public sealed class SlotWriter(IAppDbContext dbContext, InterstitialGroups interstitials)
{
    /// <summary>
    /// Проверяет вход и применяет его к слоту. <paramref name="slot"/> = null — проверка перед
    /// созданием.
    /// </summary>
    public async Task<Result> ApplyAsync(
        GridLayer layer,
        Slot? slot,
        SlotInput input,
        CancellationToken cancellationToken
    )
    {
        // Перекрытие внутри слоя запрещено: два слота на одну минуту сделали бы выбор неоднозначным.
        if (
            layer.HasOverlap(
                input.Weekday,
                input.TargetStart,
                input.TargetDurationMinutes,
                slot?.Id
            )
        )
            return Result.Failure(TemplateErrors.SlotsOverlap);

        if (input.SlotKind == SlotKind.Content)
        {
            if (input.GroupId is not { } groupId)
                return Result.Failure(TemplateErrors.GroupRequired);

            // Заведённая тут же группа ещё не в базе: импорт сетки приносит группы с собой и ставит
            // слоты в том же прогоне, а запрос увидел бы их только после сохранения.
            var known =
                dbContext.Groups.Local.Any(g => g.Id == groupId)
                || await dbContext.Groups.AnyAsync(g => g.Id == groupId, cancellationToken);
            if (!known)
                return Result.Failure(TemplateErrors.GroupNotFound);

            // Ролики в слоте — не вкусовщина, а поломка эфира: планировщик поставит их подряд
            // как программы, и зритель получит полосу рекламы вместо передачи.
            if (await interstitials.IsInterstitialAsync(groupId, cancellationToken))
                return Result.Failure(TemplateErrors.InterstitialGroupInSlot);
        }

        if (input.SlotKind == SlotKind.Repeat && input.RepeatSource is null)
            return Result.Failure(TemplateErrors.RepeatSourceRequired);

        var target =
            slot
            ?? layer.AddSlot(
                input.Title,
                input.TargetStart,
                input.TargetDurationMinutes,
                input.Daypart,
                input.SlotKind,
                input.Weekday
            );

        target.UpdateTiming(
            input.Weekday,
            input.TargetStart,
            input.TargetDurationMinutes,
            input.Daypart,
            input.IsAnchor,
            input.MaxDriftMinutes,
            input.SnapToMinutes
        );
        target.UpdateContent(
            new SlotContent(
                input.Title,
                input.SlotKind,
                input.GroupId,
                input.Strategy?.ToJson(),
                input.RepeatSource?.ToJson(),
                input.BlockMode,
                input.BlockValue,
                input.OverflowPolicy,
                input.JunctionBetweenId,
                input.JunctionAfterId
            )
        );

        return Result.Success();
    }

    /// <summary>Загружает шаблон целиком по слою — правка слота меняет ревизию всего шаблона.</summary>
    public Task<ScheduleTemplate?> LoadTemplateByLayerAsync(
        Guid layerId,
        CancellationToken cancellationToken
    ) =>
        dbContext
            .ScheduleTemplates.Include(t => t.Layers)
                .ThenInclude(l => l.Slots)
            .AsSplitQuery()
            .FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken);

    /// <summary>Загружает шаблон по слоту.</summary>
    public Task<ScheduleTemplate?> LoadTemplateBySlotAsync(
        Guid slotId,
        CancellationToken cancellationToken
    ) =>
        dbContext
            .ScheduleTemplates.Include(t => t.Layers)
                .ThenInclude(l => l.Slots)
            .AsSplitQuery()
            .FirstOrDefaultAsync(
                t => t.Layers.Any(l => l.Slots.Any(s => s.Id == slotId)),
                cancellationToken
            );
}
