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

namespace TeleWave.Application.Programming.Templates.Junctions;

public sealed class ListJunctionsQueryHandler(IAppDbContext dbContext)
    : IQueryHandler<ListJunctionsQuery, IReadOnlyList<JunctionTemplateDto>>
{
    public async Task<IReadOnlyList<JunctionTemplateDto>> Handle(
        ListJunctionsQuery query,
        CancellationToken cancellationToken
    )
    {
        var junctions = await dbContext
            .JunctionTemplates.AsNoTracking()
            .Include(j => j.Elements)
            .OrderBy(j => j.Name)
            .ToListAsync(cancellationToken);

        // Имена групп и заставок резолвим одним проходом — редактор показывает их сразу.
        var groupIds = Ids(junctions, e => e.GroupId);
        var groupNames = await dbContext
            .Groups.AsNoTracking()
            .Where(g => groupIds.Contains(g.Id))
            .ToDictionaryAsync(g => g.Id, g => g.Name, cancellationToken);

        var templateIds = Ids(junctions, e => e.BumperTemplateId);
        var bumpers = await dbContext
            .BumperTemplates.AsNoTracking()
            .Where(t => templateIds.Contains(t.Id))
            .Include(t => t.Variants)
            .ToListAsync(cancellationToken);
        var bumperNames = bumpers.ToDictionary(t => t.Id, t => t.Name);
        var variantNames = bumpers.SelectMany(t => t.Variants).ToDictionary(v => v.Id, v => v.Name);

        var usage = await ChannelUsageAsync(cancellationToken);

        return junctions
            .Select(j => new JunctionTemplateDto(
                j.Id,
                j.Name,
                j.MaxTotalSeconds,
                usage.GetValueOrDefault(j.Id),
                j.Elements.OrderBy(e => e.Position)
                    .Select(e => new JunctionElementDto(
                        e.Id,
                        e.Position,
                        e.Kind,
                        e.GroupId,
                        Lookup(groupNames, e.GroupId),
                        e.BumperTemplateId,
                        Lookup(bumperNames, e.BumperTemplateId),
                        e.BumperVariantId,
                        Lookup(variantNames, e.BumperVariantId),
                        e.AmountMode,
                        e.AmountValue,
                        e.IsRequired,
                        e.ChoiceKey,
                        e.ChoiceWeight,
                        JunctionConditions.FromJson(e.ConditionsJson)
                    ))
                    .ToList()
            ))
            .ToList();
    }

    /// <summary>Сколько каналов ссылается на каждый стык — слотами сетки либо стыком по умолчанию.</summary>
    private async Task<Dictionary<Guid, int>> ChannelUsageAsync(CancellationToken cancellationToken)
    {
        var viaSlots = await (
            from slot in dbContext.Slots.AsNoTracking()
            join layer in dbContext.GridLayers.AsNoTracking() on slot.LayerId equals layer.Id
            join template in dbContext.ScheduleTemplates.AsNoTracking()
                on layer.TemplateId equals template.Id
            where slot.JunctionBetweenId != null || slot.JunctionAfterId != null
            select new
            {
                template.ChannelId,
                slot.JunctionBetweenId,
                slot.JunctionAfterId,
            }
        ).ToListAsync(cancellationToken);

        var viaDefault = await dbContext
            .ScheduleTemplates.AsNoTracking()
            .Where(t => t.DefaultJunctionId != null)
            .Select(t => new { t.ChannelId, t.DefaultJunctionId })
            .ToListAsync(cancellationToken);

        var pairs = viaSlots
            .SelectMany(s =>
                new[] { s.JunctionBetweenId, s.JunctionAfterId }
                    .Where(id => id is not null)
                    .Select(id => (JunctionId: id!.Value, s.ChannelId))
            )
            .Concat(viaDefault.Select(d => (JunctionId: d.DefaultJunctionId!.Value, d.ChannelId)));

        return pairs.Distinct().GroupBy(p => p.JunctionId).ToDictionary(g => g.Key, g => g.Count());
    }

    private static List<Guid> Ids(
        IEnumerable<Domain.Programming.JunctionTemplate> junctions,
        Func<Domain.Programming.JunctionElement, Guid?> selector
    ) =>
        junctions
            .SelectMany(j => j.Elements)
            .Select(selector)
            .Where(id => id is not null)
            .Select(id => id!.Value)
            .Distinct()
            .ToList();

    private static string? Lookup(IReadOnlyDictionary<Guid, string> names, Guid? id) =>
        id is { } value && names.TryGetValue(value, out var name) ? name : null;
}
