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