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.CopyTemplate;

public sealed class CopyTemplateCommandHandler(IAppDbContext dbContext)
    : ICommandHandler<CopyTemplateCommand, Result<CopyTemplateResultDto>>
{
    public async Task<Result<CopyTemplateResultDto>> Handle(
        CopyTemplateCommand command,
        CancellationToken cancellationToken
    )
    {
        var target = await dbContext.Channels.FirstOrDefaultAsync(
            c => c.Id == command.TargetChannelId,
            cancellationToken
        );
        if (target is null)
            return Result.Failure<CopyTemplateResultDto>(ChannelErrors.NotFound);

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

        // Прежняя сетка приёмника заменяется целиком: слить две сетки автоматически нельзя,
        // а «добавить поверх» дало бы кашу из пересекающихся слотов.
        var existing = await dbContext
            .ScheduleTemplates.Where(t => t.ChannelId == target.Id)
            .ToListAsync(cancellationToken);
        dbContext.ScheduleTemplates.RemoveRange(existing);

        var copyTemplate = ScheduleTemplate.Create(target.Id, source.Name);
        copyTemplate.SetFallbackGroup(source.FallbackGroupId);
        copyTemplate.SetRules(source.RulesJson);
        // Стыки и заставки общие для всех каналов — копия ссылается на те же, без перевешивания.
        copyTemplate.SetDefaultJunction(source.DefaultJunctionId);

        var (layers, slots) = CopyGrid(source, copyTemplate);

        dbContext.ScheduleTemplates.Add(copyTemplate);
        target.SetTemplate(copyTemplate.Id);

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

    /// <summary>Переносит слои со слотами. Возвращает, сколько слоёв (кроме фонового) и слотов скопировано.</summary>
    private static (int Layers, int Slots) CopyGrid(
        ScheduleTemplate source,
        ScheduleTemplate copyTemplate
    )
    {
        var layers = 0;
        var slots = 0;

        foreach (var layer in source.Layers.OrderByDescending(l => l.Priority))
        {
            // Фоновый слой у нового шаблона уже есть — в него переносим слоты, а не заводим второй.
            var copyLayer = layer.IsBackground
                ? copyTemplate.Background!
                : copyTemplate.AddLayer(layer.Name, layer.Priority);
            copyLayer.Update(layer.Name, layer.Priority, layer.ApplicabilityJson, layer.IsEnabled);
            if (!layer.IsBackground)
                layers++;

            foreach (var slot in layer.Slots)
            {
                CopySlot(slot, copyLayer);
                slots++;
            }
        }

        return (layers, slots);
    }

    private static void CopySlot(Slot slot, GridLayer copyLayer)
    {
        var copySlot = copyLayer.AddSlot(
            slot.Title,
            slot.TargetStart,
            slot.TargetDurationMinutes,
            slot.Daypart,
            slot.SlotKind,
            slot.Weekday
        );
        copySlot.UpdateTiming(
            slot.Weekday,
            slot.TargetStart,
            slot.TargetDurationMinutes,
            slot.Daypart,
            slot.IsAnchor,
            slot.MaxDriftMinutes,
            slot.SnapToMinutes
        );
        copySlot.UpdateContent(
            new SlotContent(
                slot.Title,
                slot.SlotKind,
                slot.GroupId,
                slot.StrategyJson,
                slot.RepeatSourceJson,
                slot.BlockMode,
                slot.BlockValue,
                slot.OverflowPolicy,
                slot.JunctionBetweenId,
                slot.JunctionAfterId
            )
        );
    }
}
