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

namespace TeleWave.Application.Metadata.FindMissingEpisodes;

public sealed class FindMissingEpisodesQueryHandler(
    IAppDbContext dbContext,
    IMetadataProviderResolver resolver
) : IQueryHandler<FindMissingEpisodesQuery, Result<MissingEpisodesReport>>
{
    public async Task<Result<MissingEpisodesReport>> Handle(
        FindMissingEpisodesQuery query,
        CancellationToken cancellationToken
    )
    {
        var show = await dbContext
            .Shows.AsNoTracking()
            .Include(s => s.Episodes)
            .FirstOrDefaultAsync(s => s.Id == query.ShowId, cancellationToken);
        if (show is null)
            return Result.Failure<MissingEpisodesReport>(ShowErrors.NotFound);

        // Сезоны и серии есть только у сериала: у полнометражки и ролика запрос к источнику ушёл бы
        // в /tv/{id} с идентификатором фильма и вернул бы либо пусто, либо чужие данные.
        if (show.Kind != ShowKind.Series)
            return Result.Failure<MissingEpisodesReport>(MetadataErrors.EpisodesForSeriesOnly);

        if (string.IsNullOrEmpty(show.MetadataExternalId))
            return Result.Failure<MissingEpisodesReport>(MetadataErrors.NoLinkedSource);

        var provider = show.MetadataProvider is { } key ? resolver.Resolve(key) : null;
        if (provider is null)
            return Result.Failure<MissingEpisodesReport>(MetadataErrors.ProviderNotAvailable);

        // Имена файлов — чтобы распознать номера у серий, где они ещё не проставлены.
        var assetIds = show.Episodes.Select(e => e.MediaAssetId).Distinct().ToList();
        var names = await dbContext
            .MediaAssets.AsNoTracking()
            .Where(a => assetIds.Contains(a.Id))
            .Select(a => new { a.Id, a.OriginalFileName })
            .ToDictionaryAsync(a => a.Id, a => a.OriginalFileName, cancellationToken);

        var loadedBySeason = LoadedEpisodeNumbers.GroupBySeason(show, names);

        var seasons = new List<SeasonGapDto>();
        foreach (var season in loadedBySeason.Keys.OrderBy(s => s))
        {
            var loaded = loadedBySeason[season];
            var expected = await provider.GetSeasonEpisodeCountAsync(
                show.MetadataExternalId,
                season,
                cancellationToken
            );
            var missing = expected is { } exp
                ? Enumerable.Range(1, exp).Where(n => !loaded.Contains(n)).ToList()
                : [];
            seasons.Add(new SeasonGapDto(season, expected, loaded.Count, missing));
        }

        return Result.Success(new MissingEpisodesReport(seasons));
    }
}
