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

namespace TeleWave.Application.Programming.Templates.Reset;

/// <summary>
/// Сброс сетки и собранного по ней эфира — «начать канал заново», не пересоздавая его.
///
/// Прошлое и идущая сейчас запись не трогаются: инвариант общий с генератором — у зрителя нельзя
/// вырезать программу из-под носа, а история показов нужна остыванию и потолку повторов.
/// Курсоры слотов уходят каскадом вместе со слотами, поэтому сериалы после сброса начинаются
/// с начала — это и просят словом «полностью».
///
/// Снимок последнего применения остаётся: он единственная точка отката, и после сброса «Откатить»
/// вернёт сетку. Ленту это не вернёт — её пересоберёт применение.
/// </summary>
public sealed class ResetTemplateCommandHandler(IAppDbContext dbContext)
    : ICommandHandler<ResetTemplateCommand, Result<ResetTemplateResultDto>>
{
    public async Task<Result<ResetTemplateResultDto>> Handle(
        ResetTemplateCommand command,
        CancellationToken cancellationToken
    )
    {
        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<ResetTemplateResultDto>(ChannelErrors.TemplateNotFound);

        var now = DateTimeOffset.UtcNow;

        // Удаление ленты идёт мимо change-tracker (ExecuteDelete), снятие слотов — через него:
        // связывает их в одну операцию только явная транзакция. Лок канала — тот же, что берёт
        // генератор: фоновый тик не должен дописывать хвост в момент сброса.
        await using var transaction = await dbContext.BeginTransactionAsync(cancellationToken);
        await dbContext.AcquireChannelLockAsync(command.ChannelId, cancellationToken);

        var entries = await dbContext
            .ScheduleEntries.Where(e => e.ChannelId == command.ChannelId && e.StartsAtUtc >= now)
            .ExecuteDeleteAsync(cancellationToken);

        var slots = 0;
        var layers = 0;

        foreach (var layer in template.Layers.ToList())
        {
            slots += layer.Slots.Count;

            // Нефоновый слой уходит целиком — слоты за ним каскадом. Фоновый удалить нельзя,
            // поэтому его чистим послотно.
            if (template.RemoveLayer(layer.Id))
            {
                layers++;
                continue;
            }

            foreach (var slot in layer.Slots.ToList())
                layer.RemoveSlot(slot.Id);
        }

        template.MarkChanged();

        await dbContext.SaveChangesAsync(cancellationToken);
        await transaction.CommitAsync(cancellationToken);

        return Result.Success(new ResetTemplateResultDto(slots, layers, entries));
    }
}
