using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs;
using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Extensions.QueryExtensions;
using API.Services;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Kavita.Common;
using Microsoft.EntityFrameworkCore;
namespace API.Data.Repositories;
[Flags]
public enum VolumeIncludes
{
    None = 1,
    Chapters = 2,
    People = 4,
    Tags = 8,
    /// 
    /// This will include Chapters by default
    /// 
    Files = 16
}
public interface IVolumeRepository
{
    void Add(Volume volume);
    void Update(Volume volume);
    void Remove(Volume volume);
    Task> GetFilesForVolume(int volumeId);
    Task GetVolumeCoverImageAsync(int volumeId);
    Task> GetChapterIdsByVolumeIds(IReadOnlyList volumeIds);
    Task> GetVolumesDtoAsync(int seriesId, int userId, VolumeIncludes includes = VolumeIncludes.Chapters);
    Task GetVolumeAsync(int volumeId, VolumeIncludes includes = VolumeIncludes.Files);
    Task GetVolumeDtoAsync(int volumeId, int userId);
    Task> GetVolumesForSeriesAsync(IList seriesIds, bool includeChapters = false);
    Task> GetVolumes(int seriesId);
    Task GetVolumeByIdAsync(int volumeId);
    Task> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
    Task> GetCoverImagesForLockedVolumesAsync();
}
public class VolumeRepository : IVolumeRepository
{
    private readonly DataContext _context;
    private readonly IMapper _mapper;
    public VolumeRepository(DataContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }
    public void Add(Volume volume)
    {
        _context.Volume.Add(volume);
    }
    public void Update(Volume volume)
    {
        _context.Entry(volume).State = EntityState.Modified;
    }
    public void Remove(Volume volume)
    {
        _context.Volume.Remove(volume);
    }
    /// 
    /// Returns a list of non-tracked files for a given volume.
    /// 
    /// 
    /// 
    public async Task> GetFilesForVolume(int volumeId)
    {
        return await _context.Chapter
            .Where(c => volumeId == c.VolumeId)
            .Include(c => c.Files)
            .SelectMany(c => c.Files)
            .AsSplitQuery()
            .AsNoTracking()
            .ToListAsync();
    }
    /// 
    /// Returns the cover image file for the given volume
    /// 
    /// 
    /// 
    public async Task GetVolumeCoverImageAsync(int volumeId)
    {
        return await _context.Volume
            .Where(v => v.Id == volumeId)
            .Select(v => v.CoverImage)
            .SingleOrDefaultAsync();
    }
    /// 
    /// Returns all chapter Ids belonging to a list of Volume Ids
    /// 
    /// 
    /// 
    public async Task> GetChapterIdsByVolumeIds(IReadOnlyList volumeIds)
    {
        return await _context.Chapter
            .Where(c => volumeIds.Contains(c.VolumeId))
            .Select(c => c.Id)
            .ToListAsync();
    }
    /// 
    /// Returns all volumes that contain a seriesId in passed array.
    /// 
    /// 
    /// Include chapter entities
    /// 
    public async Task> GetVolumesForSeriesAsync(IList seriesIds, bool includeChapters = false)
    {
        var query = _context.Volume
            .Where(v => seriesIds.Contains(v.SeriesId));
        if (includeChapters)
        {
            query = query
                .Includes(VolumeIncludes.Chapters)
                .AsSplitQuery();
        }
        var volumes =  await query.ToListAsync();
        foreach (var volume in volumes)
        {
            volume.Chapters = volume.Chapters.OrderBy(c => c.SortOrder).ToList();
        }
        return volumes;
    }
    /// 
    /// Returns an individual Volume including Chapters and Files and Reading Progress for a given volumeId
    /// 
    /// 
    /// 
    /// 
    public async Task GetVolumeDtoAsync(int volumeId, int userId)
    {
        var volume = await _context.Volume
            .Where(vol => vol.Id == volumeId)
            .Includes(VolumeIncludes.Chapters | VolumeIncludes.Files)
            .AsSplitQuery()
            .OrderBy(v => v.MinNumber)
            .ProjectTo(_mapper.ConfigurationProvider)
            .FirstOrDefaultAsync(vol => vol.Id == volumeId);
        if (volume == null) return null;
        var volumeList = new List() {volume};
        await AddVolumeModifiers(userId, volumeList);
        return volumeList[0];
    }
    /// 
    /// Returns the full Volumes including Chapters and Files for a given series
    /// 
    /// 
    /// 
    public async Task> GetVolumes(int seriesId)
    {
        return await _context.Volume
            .Where(vol => vol.SeriesId == seriesId)
            .Includes(VolumeIncludes.Chapters | VolumeIncludes.Files)
            .AsSplitQuery()
            .OrderBy(vol => vol.MinNumber)
            .ToListAsync();
    }
    /// 
    /// Returns a single volume with Chapter and Files
    /// 
    /// 
    /// 
    public async Task GetVolumeAsync(int volumeId, VolumeIncludes includes = VolumeIncludes.Files)
    {
        return await _context.Volume
            .Includes(includes)
            .AsSplitQuery()
            .SingleOrDefaultAsync(vol => vol.Id == volumeId);
    }
    /// 
    /// Returns all volumes for a given series with progress information attached. Includes all Chapters as well.
    /// 
    /// 
    /// 
    /// 
    public async Task> GetVolumesDtoAsync(int seriesId, int userId, VolumeIncludes includes = VolumeIncludes.Chapters)
    {
        var volumes =  await _context.Volume
            .Where(vol => vol.SeriesId == seriesId)
            .Includes(includes)
            .OrderBy(volume => volume.MinNumber)
            .ProjectTo(_mapper.ConfigurationProvider)
            .AsSplitQuery()
            .ToListAsync();
        await AddVolumeModifiers(userId, volumes);
        return volumes;
    }
    public async Task GetVolumeByIdAsync(int volumeId)
    {
        return await _context.Volume.FirstOrDefaultAsync(x => x.Id == volumeId);
    }
    public async Task> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat)
    {
        var extension = encodeFormat.GetExtension();
        return await _context.Volume
            .Includes(VolumeIncludes.Chapters)
            .Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(extension))
            .AsSplitQuery()
            .ToListAsync();
    }
    private async Task AddVolumeModifiers(int userId, IReadOnlyCollection volumes)
    {
        var volIds = volumes.Select(s => s.Id);
        var userProgress = await _context.AppUserProgresses
            .Where(p => p.AppUserId == userId && volIds.Contains(p.VolumeId))
            .AsNoTracking()
            .ToListAsync();
        foreach (var v in volumes)
        {
            foreach (var c in v.Chapters)
            {
                var progresses = userProgress.Where(p => p.ChapterId == c.Id).ToList();
                if (progresses.Count == 0) continue;
                c.PagesRead = progresses.Sum(p => p.PagesRead);
                c.LastReadingProgressUtc = progresses.Max(p => p.LastModifiedUtc);
                c.LastReadingProgress = progresses.Max(p => p.LastModified);
            }
            v.PagesRead = userProgress
                .Where(p => p.VolumeId == v.Id)
                .Sum(p => p.PagesRead);
        }
    }
    /// 
    /// Returns cover images for locked chapters
    /// 
    /// 
    public async Task> GetCoverImagesForLockedVolumesAsync()
    {
        return (await _context.Volume
            .Where(c => c.CoverImageLocked)
            .Select(c => c.CoverImage)
            .Where(t => !string.IsNullOrEmpty(t))
            .ToListAsync())!;
    }
}