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

namespace TeleWave.Application.Programming.Groups.GetGroup;

public sealed class GetGroupQueryHandler(
    IAppDbContext dbContext,
    GroupElementResolver resolver,
    DynamicGroupResolver dynamicResolver
) : IQueryHandler<GetGroupQuery, Result<GroupDto>>
{
    public async Task<Result<GroupDto>> Handle(
        GetGroupQuery query,
        CancellationToken cancellationToken
    )
    {
        var group = await dbContext
            .Groups.AsNoTracking()
            .Include(g => g.Items)
            .FirstOrDefaultAsync(g => g.Id == query.Id, cancellationToken);
        if (group is null)
            return Result.Failure<GroupDto>(GroupErrors.NotFound);

        // Состав считаем на месте: у динамической группы он живёт в правиле, и показать список
        // позиций вместо него значило бы показать одни поправки.
        var composition = await dynamicResolver.ResolveAsync(group, cancellationToken);
        var info = await resolver.ResolveAsync(
            composition.Select(e => (e.Kind, e.Id)),
            cancellationToken
        );

        // Идентификатор позиции есть только у ручных поправок: найденному правилом строки в таблице
        // не отвечает, и вес с порядком ему не правят — у него их нет.
        var itemIds = group
            .Items.Where(i => i.Role != GroupItemRole.Excluded)
            .ToDictionary(i => (i.ElementKind, i.ElementId), i => i.Id);

        var items = composition
            .Select(element =>
            {
                info.TryGetValue((element.Kind, element.Id), out var resolved);
                return new GroupItemDto(
                    itemIds.TryGetValue((element.Kind, element.Id), out var itemId) ? itemId : null,
                    element.Kind,
                    element.Id,
                    // Элемент мог исчезнуть из библиотеки между чисткой и чтением — показываем прочерк,
                    // а не роняем весь экран группы.
                    resolved?.Name
                        ?? "—",
                    element.Weight,
                    element.Position,
                    resolved?.UnitCount ?? 0,
                    resolved?.ShowKind,
                    resolved?.Audience,
                    resolved?.Year,
                    resolved?.PosterImageId,
                    element.Pinned
                );
            })
            .ToList();

        var excluded = await ExcludedAsync(group, cancellationToken);

        return Result.Success(
            new GroupDto(
                group.Id,
                group.Name,
                group.Description,
                group.Mode,
                GroupFilter.FromJson(group.FilterJson),
                items.Count,
                items.Sum(i => i.UnitCount),
                // Цифры карточки считаем по свежему составу, а не по кэшу: кэш пишут команды,
                // запросу состояние менять нельзя, а показывать устаревшее рядом с актуальным
                // списком — вводить в заблуждение.
                composition
                    .Select(e =>
                        info.TryGetValue((e.Kind, e.Id), out var r)
                            ? r.TotalDuration
                            : TimeSpan.Zero
                    )
                    .Aggregate(TimeSpan.Zero, (sum, d) => sum + d)
                    .TotalSeconds,
                group.StatsComputedAt,
                items,
                excluded
            )
        );
    }

    /// <summary>Исключённое показываем отдельным списком: иначе непонятно, почему шоу нет в составе.</summary>
    private async Task<IReadOnlyList<GroupExcludedDto>> ExcludedAsync(
        Domain.Programming.Group group,
        CancellationToken cancellationToken
    )
    {
        var excluded = group
            .Items.Where(i => i.Role == GroupItemRole.Excluded)
            .OrderBy(i => i.Position)
            .ToList();
        if (excluded.Count == 0)
            return [];

        var info = await resolver.ResolveAsync(
            excluded.Select(i => (i.ElementKind, i.ElementId)),
            cancellationToken
        );

        return
        [
            .. excluded.Select(i =>
            {
                info.TryGetValue((i.ElementKind, i.ElementId), out var element);
                return new GroupExcludedDto(i.ElementKind, i.ElementId, element?.Name ?? "—");
            }),
        ];
    }
}
