using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudDb;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Repository;


public class FileRepository : IFileRepository
{
    private readonly IQueryable<FileInfo> dbSetConfigured;
    private readonly IAsbCloudDbContext db;

    public FileRepository(IAsbCloudDbContext db)
    {
        this.db = db;
        this.dbSetConfigured = db.Files
            .Include(f => f.Author)
                .ThenInclude(u => u!.Company)
                    .ThenInclude(c => c.CompanyType)
            .Include(f => f.FileMarks)
                .ThenInclude(m => m.User)
            .Include(f => f.Well);
    }

    private IQueryable<FileInfo> BuildQuery(FileRequest request)
    {
        var query = dbSetConfigured
            .Where(e => e.IdWell == request.IdWell);

        if (request.IdCategory is not null)
            query = query.Where(x => x.IdCategory == request.IdCategory);

        if (request.IsDeleted is not null)
            query = query.Where(x => x.IsDeleted == request.IsDeleted);

        if (request.CompanyNamePart is not null)
            query = query.Where(e => e.Author != null && e.Author.Company.Caption.ToLower().Contains(request.CompanyNamePart.ToLower())); 

        if (request.FileNamePart is not null)
            query = query.Where(e => e.Name.ToLower().Contains(request.FileNamePart.ToLower()));

        if (request.Begin is not null)
        {
            var beginUtc = request.Begin.Value.ToUniversalTime();
            query = query.Where(e => e.UploadDate >= beginUtc);
        }

        if (request.End is not null)
        {
            var endUtc = request.End.Value.ToUniversalTime();
            query = query.Where(e => e.UploadDate <= endUtc);
        }

        if (request?.SortFields?.Any() == true)
        {
            query = query.SortBy(request.SortFields);
        }
        else
            query = query
                .OrderBy(o => o.UploadDate);

        return query;
    }

    public async Task<IEnumerable<FileInfoDto>> GetInfosAsync(FileRequest request, CancellationToken token)
    {
        var query = BuildQuery(request);

        var entities = await query
            .SkipTake(request.Skip, request.Take)
            .AsNoTracking()
            .ToListAsync(token)
            .ConfigureAwait(false);

        var dtos = entities.Select(e => Convert(e));
        return dtos;
    }

    public async Task<PaginationContainer<FileInfoDto>> GetInfosPaginatedAsync(FileRequest request, CancellationToken token = default)
    {
        var skip = request.Skip ?? 0;
        var take = request.Take ?? 32;

        var query = BuildQuery(request);

        var result = new PaginationContainer<FileInfoDto>()
        {
            Skip = skip,
            Take = take,
            Count = await query.CountAsync(token),
        };

        if (result.Count == 0)
            return result;

        var entities = await query
            .SkipTake(skip, take)
            .AsNoTracking()
            .ToListAsync(token)
            .ConfigureAwait(false);

        result.Items = entities.Select(e => Convert(e));

        return result;
    }

    public async Task<IEnumerable<FileInfoDto>> GetInfoByIdsAsync(IEnumerable<int> idsFile, CancellationToken token)
    {
        var result = Enumerable.Empty<FileInfoDto>();

        var entities = await dbSetConfigured
            .AsNoTracking()
            .Where(f => idsFile.Contains(f.Id))
            .ToListAsync(token)
            .ConfigureAwait(false);

        if (entities is not null)
            result = entities.Select(entity => Convert(entity));

        return result;
    }

    public async Task<int> MarkAsDeletedAsync(int idFile, CancellationToken token = default)
    {
        var fileInfo = await db.Files.FirstOrDefaultAsync(f => f.Id == idFile, token).ConfigureAwait(false);

        if (fileInfo is null)
            return 0;

        fileInfo.IsDeleted = true;

        return await db.SaveChangesAsync(token).ConfigureAwait(false);
    }

    public async Task<IEnumerable<FileInfoDto>> DeleteAsync(IEnumerable<int> ids, CancellationToken token)
    {
        var query = dbSetConfigured
            .Where(f => ids.Contains(f.Id));

            var files = await query.ToListAsync(token);

            var filesDtos = files.Select(x => Convert(x));

            db.Files.RemoveRange(query);
            await db.SaveChangesAsync(token).ConfigureAwait(false);

            return filesDtos;
        }

    public async Task<FileInfoDto> GetByMarkId(int idMark, CancellationToken token)
    {
        var entity = await dbSetConfigured
            .FirstOrDefaultAsync(f => f.FileMarks.Any(m => m.Id == idMark), token)
            .ConfigureAwait(false);

        FileInfoDto dto = Convert(entity!);
        return dto;
    }

    public async Task<int> CreateFileMarkAsync(FileMarkDto fileMarkDto, int idUser, CancellationToken token)
    {
        var fileMark = await db.FileMarks
            .FirstOrDefaultAsync(m => m.IdFile == fileMarkDto.IdFile &&
                m.IdMarkType == fileMarkDto.IdMarkType &&
                m.IdUser == idUser &&
                m.IsDeleted == false,
                token)
            .ConfigureAwait(false);

        if (fileMark is not null)
            return 0;

        var newFileMark = fileMarkDto.Adapt<FileMark>();
        newFileMark.Id = default;
        newFileMark.DateCreated = DateTimeOffset.UtcNow;
        newFileMark.IdUser = idUser;
        newFileMark.User = null!;

        db.FileMarks.Add(newFileMark);
        return await db.SaveChangesAsync(token);
    }

    public async Task<int> MarkFileMarkAsDeletedAsync(IEnumerable<int> idsMarks, CancellationToken token)
    {
        var fileMarkQuery = db.FileMarks
            .Where(m => idsMarks.Contains(m.Id));

        foreach (var fileMark in fileMarkQuery)
            fileMark.IsDeleted = true;

        return await db.SaveChangesAsync(token);
    }

    public async Task<IEnumerable<FileInfoDto>> GetAllAsync(CancellationToken token)
        => await dbSetConfigured.AsNoTracking()
            .Select(x => Convert(x))
            .ToListAsync(token)
            .ConfigureAwait(false);

    public async Task<FileInfoDto?> GetOrDefaultAsync(int id, CancellationToken token)
    {
        var entity = await dbSetConfigured
            .AsNoTracking()
            .FirstOrDefaultAsync(f => f.Id == id, token)
            .ConfigureAwait(false);

        if (entity is null)
            return null;

        var dto = Convert(entity);
        return dto;
    }

    public FileInfoDto? GetOrDefault(int id)
    {
        var entity = dbSetConfigured
            .AsNoTracking()
            .FirstOrDefault(f => f.Id == id);

        if (entity is null)
            return null;

        var dto = Convert(entity);
        return dto;
    }

    public async Task<int> InsertAsync(FileInfoDto newItem, CancellationToken token)
    {
        var fileInfo = new FileInfo()
        {
            IdWell = newItem.IdWell,
            IdAuthor = newItem.IdAuthor,
            IdCategory = newItem.IdCategory,
            Name = newItem.Name,
            UploadDate = DateTimeOffset.UtcNow,
            IsDeleted = false,
            Size = newItem.Size,
        };

        var entry = db.Files.Add(fileInfo);
        await db.SaveChangesAsync(token).ConfigureAwait(false);
        return entry.Entity.Id;
    }

    public Task<int> InsertRangeAsync(IEnumerable<FileInfoDto> newItems, CancellationToken token)
    {
        throw new NotImplementedException();
    }

    public Task<int> UpdateAsync(FileInfoDto item, CancellationToken token)
    {
        throw new NotImplementedException();
    }

    public Task<int> DeleteAsync(int id, CancellationToken token)
    {
        throw new NotImplementedException();
    }

    private static FileInfoDto Convert(FileInfo entity)
    {
        var timezoneOffset = entity.Well.Timezone?.Hours ?? 5;
        return Convert(entity, timezoneOffset);
    }

    private static FileInfoDto Convert(FileInfo entity, double timezoneOffset)
    {
        var dto = entity.Adapt<FileInfoDto>();
        dto.UploadDate = entity.UploadDate.ToOffset(TimeSpan.FromHours(timezoneOffset));
        dto.FileMarks = entity.FileMarks.Select(m =>
        {
            var mark = m.Adapt<FileMarkDto>();
            mark.DateCreated = m.DateCreated.ToOffset(TimeSpan.FromHours(timezoneOffset));
            return mark;
        });
        return dto;
    }
}