using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services;
using DocumentFormat.OpenXml.Drawing.Charts;
using DocumentFormat.OpenXml.Wordprocessing;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Org.BouncyCastle.Asn1.Ocsp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Repository
{
#nullable enable
    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);

            double timezoneOffsetHours = query.FirstOrDefault()
                ?.Well.Timezone.Hours ?? 5d;

            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.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.ToUtcDateTimeOffset(timezoneOffsetHours);
                query = query.Where(e => e.UploadDate >= beginUtc);
            }

            if (request.End is not null)
            {
                var endUtc = request.End.Value.ToUtcDateTimeOffset(timezoneOffsetHours);
                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)).ToList();

            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) && f.IsDeleted);

            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 = DateTime.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 = DateTime.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.ToRemoteDateTime(timezoneOffset);
            dto.FileMarks = entity.FileMarks.Select(m =>
            {
                var mark = m.Adapt<FileMarkDto>();
                mark.DateCreated = m.DateCreated.ToRemoteDateTime(timezoneOffset);
                return mark;
            });
            return dto;
        }
    }
#nullable disable
}