using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using TeleWave.Domain.Programming;

namespace TeleWave.Application.Programming.Groups;

/// <summary>Сводка по элементу группы: как показать и сколько эфира он даёт.</summary>
public sealed record GroupElementInfo(
    string Name,
    int UnitCount,
    TimeSpan TotalDuration,
    ShowKind? ShowKind,
    ShowAudience? Audience,
    int? Year,
    Guid? PosterImageId
);

/// <summary>
/// Разворачивает ссылки группы (шоу либо коллекция) в данные для отображения и статистики.
/// Единица воспроизведения — серия: у фильма она одна, у сериала их столько же, сколько серий,
/// у коллекции — сумма по всем её частям.
///
/// Длительность считается только по готовым ассетам: в группе может лежать шоу, чьё медиа ещё
/// обрабатывается, и включать его в «118 часов» значило бы обещать эфир, которого пока нет.
/// </summary>
public sealed class GroupElementResolver(IAppDbContext dbContext)
{
    public async Task<
        IReadOnlyDictionary<(GroupElementKind Kind, Guid Id), GroupElementInfo>
    > ResolveAsync(
        IEnumerable<(GroupElementKind Kind, Guid Id)> elements,
        CancellationToken cancellationToken
    )
    {
        var requested = elements.Distinct().ToList();
        if (requested.Count == 0)
            return new Dictionary<(GroupElementKind, Guid), GroupElementInfo>();

        var collectionIds = requested
            .Where(e => e.Kind == GroupElementKind.Collection)
            .Select(e => e.Id)
            .ToList();

        var collections = await dbContext
            .Collections.AsNoTracking()
            .Where(c => collectionIds.Contains(c.Id))
            .Select(c => new
            {
                c.Id,
                c.Name,
                c.PosterImageId,
                ShowIds = c.Items.OrderBy(i => i.Position).Select(i => i.ShowId).ToList(),
            })
            .ToListAsync(cancellationToken);

        var showIds = requested
            .Where(e => e.Kind == GroupElementKind.Show)
            .Select(e => e.Id)
            .Concat(collections.SelectMany(c => c.ShowIds))
            .Distinct()
            .ToList();

        var shows = await dbContext
            .Shows.AsNoTracking()
            .Where(s => showIds.Contains(s.Id))
            .Select(s => new
            {
                s.Id,
                s.Name,
                s.Kind,
                s.Audience,
                s.Year,
                s.PosterImageId,
                AssetIds = s.Episodes.Select(e => e.MediaAssetId).ToList(),
            })
            .ToDictionaryAsync(s => s.Id, cancellationToken);

        var assetIds = shows.Values.SelectMany(s => s.AssetIds).Distinct().ToList();
        var durations = await dbContext
            .MediaAssets.AsNoTracking()
            .Where(a =>
                assetIds.Contains(a.Id) && a.Status == MediaAssetStatus.Ready && a.Duration != null
            )
            .Select(a => new { a.Id, a.Duration })
            .ToDictionaryAsync(a => a.Id, a => a.Duration!.Value, cancellationToken);

        TimeSpan DurationOf(IEnumerable<Guid> ids) =>
            ids.Aggregate(
                TimeSpan.Zero,
                (sum, id) => durations.TryGetValue(id, out var d) ? sum + d : sum
            );

        var result = new Dictionary<(GroupElementKind, Guid), GroupElementInfo>();

        foreach (var (kind, id) in requested)
        {
            if (kind == GroupElementKind.Show)
            {
                if (!shows.TryGetValue(id, out var show))
                    continue;
                result[(kind, id)] = new GroupElementInfo(
                    show.Name,
                    show.AssetIds.Count,
                    DurationOf(show.AssetIds),
                    show.Kind,
                    show.Audience,
                    show.Year,
                    show.PosterImageId
                );
                continue;
            }

            var collection = collections.FirstOrDefault(c => c.Id == id);
            if (collection is null)
                continue;

            var parts = collection
                .ShowIds.Select(showId => shows.TryGetValue(showId, out var s) ? s : null)
                .Where(s => s is not null)
                .ToList();
            var partAssets = parts.SelectMany(p => p!.AssetIds).ToList();

            result[(kind, id)] = new GroupElementInfo(
                collection.Name,
                partAssets.Count,
                DurationOf(partAssets),
                null,
                // Рейтинг коллекции — самый строгий среди частей: по нему отбирают в детское время.
                parts.Count == 0
                    ? null
                    : parts.Max(p => p!.Audience),
                parts.Count == 0 ? null : parts.Min(p => p!.Year),
                collection.PosterImageId
            );
        }

        return result;
    }
}
