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

namespace TeleWave.Application.Programming.Templates.Layers;

public sealed class CreateLayerCommandHandler(IAppDbContext dbContext)
    : ICommandHandler<CreateLayerCommand, Result<Guid>>
{
    public async Task<Result<Guid>> Handle(
        CreateLayerCommand command,
        CancellationToken cancellationToken
    )
    {
        var template = await LayerLoader.ByTemplateAsync(
            dbContext,
            command.TemplateId,
            cancellationToken
        );
        if (template is null)
            return Result.Failure<Guid>(TemplateErrors.NotFound);

        var layer = template.AddLayer(command.Name, command.Priority);
        template.MarkChanged();
        return Result.Success(layer.Id);
    }
}

public sealed class UpdateLayerCommandHandler(IAppDbContext dbContext)
    : ICommandHandler<UpdateLayerCommand, Result>
{
    public async Task<Result> Handle(
        UpdateLayerCommand command,
        CancellationToken cancellationToken
    )
    {
        var template = await LayerLoader.ByLayerAsync(
            dbContext,
            command.LayerId,
            cancellationToken
        );
        var layer = template?.FindLayer(command.LayerId);
        if (template is null || layer is null)
            return Result.Failure(TemplateErrors.LayerNotFound);

        layer.Update(
            command.Name,
            command.Priority,
            command.Applicability?.ToJson(),
            command.IsEnabled
        );
        template.MarkChanged();
        return Result.Success();
    }
}

public sealed class DeleteLayerCommandHandler(IAppDbContext dbContext)
    : ICommandHandler<DeleteLayerCommand, Result>
{
    public async Task<Result> Handle(
        DeleteLayerCommand command,
        CancellationToken cancellationToken
    )
    {
        var template = await LayerLoader.ByLayerAsync(
            dbContext,
            command.LayerId,
            cancellationToken
        );
        var layer = template?.FindLayer(command.LayerId);
        if (template is null || layer is null)
            return Result.Failure(TemplateErrors.LayerNotFound);

        if (layer.IsBackground)
            return Result.Failure(TemplateErrors.BackgroundLayerCannotBeDeleted);

        template.RemoveLayer(command.LayerId);
        template.MarkChanged();
        return Result.Success();
    }
}

public sealed class UpdateTemplateCommandHandler(
    IAppDbContext dbContext,
    InterstitialGroups interstitials
) : ICommandHandler<UpdateTemplateCommand, Result>
{
    public async Task<Result> Handle(
        UpdateTemplateCommand command,
        CancellationToken cancellationToken
    )
    {
        var template = await dbContext.ScheduleTemplates.FirstOrDefaultAsync(
            t => t.Id == command.TemplateId,
            cancellationToken
        );
        if (template is null)
            return Result.Failure(TemplateErrors.NotFound);

        if (command.FallbackGroupId is { } groupId)
        {
            if (!await dbContext.Groups.AnyAsync(g => g.Id == groupId, cancellationToken))
                return Result.Failure(TemplateErrors.GroupNotFound);

            // Аварийная группа закрывает каждую паузу: остаток слота, добор до якоря, пустой повтор.
            // Из роликов она превращает всё это в рекламу — ровно то, что видно в расписании
            // строками «аварийный запас» подряд.
            if (await interstitials.IsInterstitialAsync(groupId, cancellationToken))
                return Result.Failure(TemplateErrors.InterstitialFallbackGroup);
        }

        template.Rename(command.Name);
        template.SetFallbackGroup(command.FallbackGroupId);
        template.SetDefaultJunction(command.DefaultJunctionId);
        template.SetRules(command.Rules?.ToJson());
        template.MarkChanged();
        return Result.Success();
    }
}

/// <summary>Загрузка шаблона со слоями и слотами: любая правка слоя меняет ревизию всего шаблона.</summary>
internal static class LayerLoader
{
    public static Task<ScheduleTemplate?> ByTemplateAsync(
        IAppDbContext dbContext,
        Guid templateId,
        CancellationToken cancellationToken
    ) =>
        dbContext
            .ScheduleTemplates.Include(t => t.Layers)
                .ThenInclude(l => l.Slots)
            .AsSplitQuery()
            .FirstOrDefaultAsync(t => t.Id == templateId, cancellationToken);

    public static Task<ScheduleTemplate?> ByLayerAsync(
        IAppDbContext dbContext,
        Guid layerId,
        CancellationToken cancellationToken
    ) =>
        dbContext
            .ScheduleTemplates.Include(t => t.Layers)
                .ThenInclude(l => l.Slots)
            .AsSplitQuery()
            .FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken);
}
