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

namespace TeleWave.Application.Programming.Groups.DeleteGroup;

public sealed class DeleteGroupCommandHandler(IAppDbContext dbContext)
    : ICommandHandler<DeleteGroupCommand, Result>
{
    public async Task<Result> Handle(
        DeleteGroupCommand command,
        CancellationToken cancellationToken
    )
    {
        var group = await dbContext.Groups.FirstOrDefaultAsync(
            g => g.Id == command.GroupId,
            cancellationToken
        );
        if (group is null)
            return Result.Failure(GroupErrors.NotFound);

        // Слот и врезка стыка держат группу внешним ключом с Restrict — без этой проверки удаление
        // упало бы исключением БД вместо управляемой ошибки.
        var used =
            await dbContext.Slots.AnyAsync(s => s.GroupId == group.Id, cancellationToken)
            || await dbContext.JunctionElements.AnyAsync(
                e => e.GroupId == group.Id,
                cancellationToken
            );

        // Сам факт занятости и адрес — разные вопросы: адрес собирается по цепочке слой → шаблон →
        // канал, и если она неполна, имени не будет, а запрещать удаление всё равно надо.
        if (used)
            return Result.Failure(
                GroupErrors.InUse(await UsedByAsync(group.Id, cancellationToken))
            );

        await ReleaseFallbackAsync(group.Id, cancellationToken);

        dbContext.Groups.Remove(group);
        return Result.Success();
    }

    /// <summary>
    /// Снимает группу с шаблонов, где она стоит аварийной. Внешнего ключа у этой ссылки нет, и
    /// висячая проявилась бы только в эфире — генератор молча остался бы без замены для пустого
    /// слота. Блокировать удаление ею мы намеренно не будем: слот и врезка — это осознанная
    /// настройка сетки, а аварийную группу проставляет и автосборка, и снять её было нечем.
    ///
    /// Ревизия поднимается: конфигурация эфира изменилась, и лента должна пересобраться —
    /// как и от любой другой правки шаблона.
    /// </summary>
    private async Task ReleaseFallbackAsync(Guid groupId, CancellationToken cancellationToken)
    {
        var templates = await dbContext
            .ScheduleTemplates.Where(t => t.FallbackGroupId == groupId)
            .ToListAsync(cancellationToken);

        foreach (var template in templates)
        {
            template.SetFallbackGroup(null);
            template.MarkChanged();
        }
    }

    /// <summary>Кто держит группу: слоты каналов и врезки стыков.</summary>
    private async Task<string> UsedByAsync(Guid groupId, CancellationToken cancellationToken)
    {
        var slots = await (
            from slot in dbContext.Slots
            join layer in dbContext.GridLayers on slot.LayerId equals layer.Id
            join template in dbContext.ScheduleTemplates on layer.TemplateId equals template.Id
            join channel in dbContext.Channels on template.ChannelId equals channel.Id
            where slot.GroupId == groupId
            select channel.Name
        )
            .Distinct()
            .ToListAsync(cancellationToken);

        var junctions = await (
            from element in dbContext.JunctionElements
            join junction in dbContext.JunctionTemplates
                on element.JunctionTemplateId equals junction.Id
            where element.GroupId == groupId
            select junction.Name
        )
            .Distinct()
            .ToListAsync(cancellationToken);

        return UsageText.Join(
            UsageText.Part("слоты каналов", slots),
            UsageText.Part("врезки стыков", junctions)
        );
    }
}
