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

namespace TeleWave.Application.Programming.Templates.Generate;

/// <summary>
/// Создаёт слоты по плану. Слои заводятся по именам профиля и переиспользуются: повторная генерация
/// не должна плодить «Основная сетка (2)».
///
/// Эфир при этом не двигается — как и любая правка сетки, генерация только помечает шаблон
/// изменённым, а хвост пересобирает отдельная кнопка применения.
/// </summary>
public sealed class GenerateGridCommandHandler(
    IAppDbContext dbContext,
    GridPlanner planner,
    SlotWriter writer
) : ICommandHandler<GenerateGridCommand, Result<GenerateGridResultDto>>
{
    /// <summary>Приоритеты слоёв генератора: выходные обязаны перекрывать будни.</summary>
    private const int MainPriority = 10;
    private const int WeekendPriority = 20;

    public async Task<Result<GenerateGridResultDto>> Handle(
        GenerateGridCommand command,
        CancellationToken cancellationToken
    )
    {
        var planned = await planner.BuildAsync(
            command.ChannelId,
            command.Profile,
            command.Mode,
            cancellationToken
        );
        if (!planned.IsSuccess)
            return Result.Failure<GenerateGridResultDto>(planned.Error);

        var plan = planned.Value;

        var template = await dbContext
            .ScheduleTemplates.Include(t => t.Layers)
                .ThenInclude(l => l.Slots)
            .AsSplitQuery()
            .FirstOrDefaultAsync(t => t.ChannelId == command.ChannelId, cancellationToken);
        if (template is null)
            return Result.Failure<GenerateGridResultDto>(ChannelErrors.TemplateNotFound);

        var removed = 0;
        if (command.Mode == GridGenerationMode.Rebuild)
            foreach (var layer in template.Layers)
            foreach (var slot in layer.Slots.ToList())
                if (layer.RemoveSlot(slot.Id))
                    removed++;

        var created = 0;
        foreach (var slot in plan.Slots)
        {
            var layer = EnsureLayer(template, slot.Layer);
            var applied = await writer.ApplyAsync(layer, null, ToInput(slot), cancellationToken);
            if (!applied.IsSuccess)
                return Result.Failure<GenerateGridResultDto>(applied.Error);
            created++;
        }

        // Аварийная группа — то, чем закрываются паузы между слотами. Без неё в ленте остались бы
        // дыры, поэтому генератор проставляет её, если админ ещё не выбрал свою.
        if (plan.FallbackGroupId is { } fallbackGroupId && template.FallbackGroupId is null)
            template.SetFallbackGroup(fallbackGroupId);

        template.MarkChanged();
        return Result.Success(new GenerateGridResultDto(created, removed));
    }

    private static GridLayer EnsureLayer(ScheduleTemplate template, GridPlanLayer kind)
    {
        var (name, priority) =
            kind == GridPlanLayer.Weekend
                ? (GridPlanner.WeekendLayerName, WeekendPriority)
                : (GridPlanner.MainLayerName, MainPriority);

        return template.Layers.FirstOrDefault(l => !l.IsBackground && l.Name == name)
            ?? template.AddLayer(name, priority);
    }

    private static SlotInput ToInput(PlannedSlot slot) =>
        new(
            slot.Title,
            slot.Weekday,
            slot.Start,
            slot.DurationMinutes,
            slot.Daypart,
            slot.SlotKind,
            slot.GroupId,
            slot.Strategy,
            slot.RepeatSource,
            slot.BlockMode,
            slot.BlockValue,
            OverflowPolicy.ContinueNext,
            slot.IsAnchor,
            Slot.DefaultMaxDriftMinutes,
            null
        );
}
