using LiteCqrs;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
using TeleWave.Domain.Broadcast.Live;
using TeleWave.Domain.Media;

namespace TeleWave.Application.Streaming.GetLivePlaylist;

public sealed class GetLivePlaylistQueryHandler(
    IAppDbContext dbContext,
    IOptions<StreamingOptions> options
) : IQueryHandler<GetLivePlaylistQuery, Result<LivePlaylistDto>>
{
    private readonly StreamingOptions _options = options.Value;

    public async Task<Result<LivePlaylistDto>> Handle(
        GetLivePlaylistQuery query,
        CancellationToken cancellationToken
    )
    {
        var channel = await dbContext
            .Channels.AsNoTracking()
            .Where(c => c.Slug == query.Slug && c.IsEnabled)
            .Select(c => new
            {
                c.Id,
                c.EpochUtc,
                c.FillerAssetId,
            })
            .FirstOrDefaultAsync(cancellationToken);
        if (channel is null)
            return Result.Failure<LivePlaylistDto>(ChannelErrors.NotFound);

        var seg = _options.SegmentSeconds;
        var window = _options.LiveWindowSegments;

        // Загружаем записи, пересекающиеся с окном (с небольшим запасом назад).
        var windowStartTime = query.Now.AddSeconds(-(window + 2) * seg);
        var rawEntries = await dbContext
            .ScheduleEntries.AsNoTracking()
            .Where(e =>
                e.ChannelId == channel.Id
                && e.StartsAtUtc < query.Now
                && e.EndsAtUtc > windowStartTime
            )
            .OrderBy(e => e.StartsAtUtc)
            .Select(e => new
            {
                e.StartsAtUtc,
                e.EndsAtUtc,
                e.MediaAssetId,
            })
            .ToListAsync(cancellationToken);

        var assetIds = rawEntries.Select(e => e.MediaAssetId).ToList();
        if (channel.FillerAssetId is { } fillerId)
            assetIds.Add(fillerId);

        var segmentCounts = await dbContext
            .MediaAssets.AsNoTracking()
            .Where(a =>
                assetIds.Contains(a.Id)
                && a.Status == MediaAssetStatus.Ready
                && a.SegmentCount != null
            )
            .Select(a => new { a.Id, a.SegmentCount })
            .ToDictionaryAsync(a => a.Id, a => a.SegmentCount!.Value, cancellationToken);

        var entries = rawEntries
            .Where(e => segmentCounts.ContainsKey(e.MediaAssetId))
            .Select(e => new LiveEntry(
                e.StartsAtUtc,
                e.EndsAtUtc,
                e.MediaAssetId,
                segmentCounts[e.MediaAssetId]
            ))
            .ToList();

        LiveFiller? filler = null;
        if (
            channel.FillerAssetId is { } fid
            && segmentCounts.TryGetValue(fid, out var fillerSegments)
        )
            filler = new LiveFiller(fid, fillerSegments);

        var playlist = LiveWindowCalculator.Build(
            new LiveInput(query.Now, channel.EpochUtc, seg, window, entries, filler)
        );

        var dto = new LivePlaylistDto(
            playlist.MediaSequence,
            playlist.TargetDuration,
            playlist
                .Segments.Select(s => new LiveSegmentDto(s.AssetId, s.LocalIndex, s.Discontinuity))
                .ToList()
        );
        return Result.Success(dto);
    }
}
