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

namespace AsbCloudInfrastructure.Services
{
    public class FileService : IFileService
    {
        public string RootPath { get; private set; }

        private readonly IQueryable<AsbCloudDb.Model.FileInfo> dbSetConfigured;
        private readonly IAsbCloudDbContext db;

        public FileService(IAsbCloudDbContext db)
        {
            RootPath = "files";
            this.db = db;
            dbSetConfigured = db.Files
                .Include(f => f.Author)
                    .ThenInclude(u => u.Company)
                        .ThenInclude(c => c.CompanyType);
        }

        public async Task<FileInfoDto> SaveAsync(int idWell, int? idUser, int idCategory, string fileFullName, Stream fileStream, CancellationToken token = default)
        {
            //save info to db
            var fileInfo = new AsbCloudDb.Model.FileInfo()
            {
                IdWell = idWell,
                IdAuthor = idUser,
                IdCategory = idCategory,
                Name = Path.GetFileName(fileFullName),
                UploadDate = DateTime.Now,
                IsDeleted = false,
                Size = fileStream.Length,
            };

            var entry = db.Files.Add(fileInfo);
            db.SaveChanges();
            var fileId = entry.Entity.Id;
            //save stream to disk
            var relativePath = Path.Combine(RootPath, $"{idWell}",
                    $"{idCategory}", $"{fileId}" + $"{Path.GetExtension(fileFullName)}");

            Directory.CreateDirectory(Path.GetDirectoryName(relativePath));

            using (var newfileStream = new FileStream(relativePath, FileMode.Create))
            {
                await fileStream.CopyToAsync(newfileStream);
            }

            var dto = entry.Entity.Adapt<FileInfoDto>();
            return dto;
        }

        public async Task<IEnumerable<FileInfoDto>> GetInfosByCategoryAsync(int idWell,
            int idCategory, CancellationToken token = default)
        {
            var entities = await dbSetConfigured
                .Where(e => e.IdWell == idWell && e.IdCategory == idCategory)
                .AsNoTracking()
                .ToListAsync(token)
                .ConfigureAwait(false);

            var dtos = entities.Adapt<FileInfoDto>();
            return dtos;
        }

        public async Task<PaginationContainer<FileInfoDto>> GetInfosAsync(int idWell,
            int idCategory, string companyName = default, string fileName = default, DateTime begin = default,
            DateTime end = default, int skip = 0, int take = 32, CancellationToken token = default)
        {
            var query = dbSetConfigured
                .Where(e => e.IdWell == idWell &&
                    e.IdCategory == idCategory);

            query = query.Where(e => !e.IsDeleted);

            if (!string.IsNullOrEmpty(companyName))
                query = query
                        .Include(file => file.Author)
                            .ThenInclude(a => a.Company)
                        .Where(e => (e.Author == null) || (e.Author.Company == null) || e.Author.Company.Caption.Contains(companyName));

            if (!string.IsNullOrEmpty(fileName))
                query = query.Where(e => e.Name.ToLower().Contains(fileName.ToLower()));

            if (begin != default)
                query = query.Where(e => e.UploadDate >= begin);

            if (end != default)
                query = query.Where(e => e.UploadDate <= end);

            var count = await query.CountAsync(token).ConfigureAwait(false);

            var result = new PaginationContainer<FileInfoDto>(count)
            {
                Skip = skip,
                Take = take,
                Count = count,
            };

            if (count <= skip)
                return result;

            query = query.OrderBy(e => e.UploadDate);

            if (skip > 0)
                query = query.Skip(skip);
            query = query.Take(take);

            var entities = await query
                .Take(take).AsNoTracking().ToListAsync(token)
                .ConfigureAwait(false);

            var dtos = entities.Adapt<FileInfoDto>();

            result.Items.AddRange(dtos);
            return result;
        }

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

            if (entity is null)
                return null;

            var dto = entity.Adapt<FileInfoDto>();
            return dto;
        }

        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<int> DeletedAsync(int idFile, CancellationToken token)
        {
            var fileInfo = await db.Files
                .FirstOrDefaultAsync(f => f.Id == idFile, token)
                .ConfigureAwait(false);

            if (fileInfo is null)
                return 0;

            var fileName = GetFileName(fileInfo.Adapt<FileInfoDto>());
            if (File.Exists(fileName))
                File.Delete(fileName);

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

        public string GetFileName(FileInfoDto fileInfo)
        {
            var fileName = $"{fileInfo.Id}{Path.GetExtension(fileInfo.Name)}";
            fileName = Path.Combine(RootPath, fileInfo.IdWell.ToString(), fileInfo.IdCategory.ToString(), fileName);
            fileName = Path.GetFullPath(fileName);
            return fileName;
        }
    }
}