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

namespace TeleWave.Application.Programming.Planning.Preview;

public sealed class PreviewScheduleQueryHandler(
    IAppDbContext dbContext,
    GridScheduleGenerator generator
) : IQueryHandler<PreviewScheduleQuery, Result<SchedulePreviewDto>>
{
    public async Task<Result<SchedulePreviewDto>> Handle(
        PreviewScheduleQuery query,
        CancellationToken cancellationToken
    )
    {
        var channel = await dbContext
            .Channels.AsNoTracking()
            .Where(c => c.Id == query.ChannelId)
            .Select(c => new { c.UtcOffsetMinutes })
            .FirstOrDefaultAsync(cancellationToken);
        if (channel is null)
            return Result.Failure<SchedulePreviewDto>(ChannelErrors.NotFound);

        var from = DateTimeOffset.UtcNow;
        var result = await generator.PreviewAsync(
            query.ChannelId,
            from,
            query.Days,
            cancellationToken
        );
        if (result is null)
            return Result.Failure<SchedulePreviewDto>(ChannelErrors.TemplateNotFound);

        var showNames = await LoadShowNamesAsync(result.Items, cancellationToken);
        var slotTitles = await LoadSlotTitlesAsync(result.Items, cancellationToken);

        var items = result
            .Items.Select(item => new PreviewItemDto(
                item.Kind,
                item.StartsAtUtc,
                item.EndsAtUtc,
                item.ShowId,
                item.ShowId is { } showId && showNames.TryGetValue(showId, out var name)
                    ? name
                    : null,
                item.SlotId,
                item.SlotId is { } slotId && slotTitles.TryGetValue(slotId, out var title)
                    ? title
                    : null
            ))
            .ToList();

        var to = items.Count == 0 ? from : items[^1].EndsAtUtc;

        return Result.Success(
            new SchedulePreviewDto(
                from,
                to,
                channel.UtcOffsetMinutes,
                items,
                result
                    .Warnings.Select(w => new PlanningWarningDto(w.Kind, w.SlotId, w.Details))
                    .ToList()
            )
        );
    }

    private async Task<Dictionary<Guid, string>> LoadShowNamesAsync(
        IReadOnlyList<PlannedItem> items,
        CancellationToken cancellationToken
    )
    {
        var ids = items
            .Select(i => i.ShowId)
            .Where(id => id is not null && id != Guid.Empty)
            .Select(id => id!.Value)
            .Distinct()
            .ToList();
        if (ids.Count == 0)
            return [];

        return await dbContext
            .Shows.AsNoTracking()
            .Where(s => ids.Contains(s.Id))
            .ToDictionaryAsync(s => s.Id, s => s.Name, cancellationToken);
    }

    private async Task<Dictionary<Guid, string>> LoadSlotTitlesAsync(
        IReadOnlyList<PlannedItem> items,
        CancellationToken cancellationToken
    )
    {
        var ids = items
            .Select(i => i.SlotId)
            .Where(id => id is not null)
            .Select(id => id!.Value)
            .Distinct()
            .ToList();
        if (ids.Count == 0)
            return [];

        return await dbContext
            .Slots.AsNoTracking()
            .Where(s => ids.Contains(s.Id))
            .ToDictionaryAsync(s => s.Id, s => s.Title, cancellationToken);
    }
}
