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> MoveAsync(int idWell, int? idUser, int idCategory, string destinationFileName, string srcFilePath, CancellationToken token = default)
        {
            destinationFileName = Path.GetFileName(destinationFileName);
            srcFilePath = Path.GetFullPath(srcFilePath);
            if (!File.Exists(srcFilePath))
                throw new ArgumentException($"file {srcFilePath} doesn't exist", nameof(srcFilePath));

            var sysFileInfo = new System.IO.FileInfo(srcFilePath);

            //save info to db
            var fileInfo = new AsbCloudDb.Model.FileInfo()
            {
                IdWell = idWell,
                IdAuthor = idUser,
                IdCategory = idCategory,
                Name = destinationFileName,
                UploadDate = DateTime.Now,
                IsDeleted = false,
                Size = sysFileInfo.Length,
            };

            var entry = db.Files.Add(fileInfo);
            await db.SaveChangesAsync(token).ConfigureAwait(false);
            var fileId = entry.Entity.Id;
            string filePath = MakeFilePath(idWell, idCategory, destinationFileName, fileId);
            Directory.CreateDirectory(Path.GetDirectoryName(filePath));
            File.Move(srcFilePath, filePath);

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

        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 ?? 0
            };

            var entry = db.Files.Add(fileInfo);
            await db.SaveChangesAsync(token).ConfigureAwait(false);
            var fileId = entry.Entity.Id;
            //save stream to disk
            string filePath = MakeFilePath(idWell, idCategory, fileFullName, fileId);

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

            using var newfileStream = new FileStream(filePath, FileMode.Create);
            await fileStream.CopyToAsync(newfileStream, token).ConfigureAwait(false);
                await fileStream.CopyToAsync(newfileStream);

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

        private string MakeFilePath(int idWell, int idCategory, string fileFullName, int fileId)
        {
            return Path.Combine(RootPath, $"{idWell}",
                    $"{idCategory}", $"{fileId}" + $"{Path.GetExtension(fileFullName)}");
        }

        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 && e.IsDeleted == false)
                .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> DeleteAsync(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 = GetUrl(fileInfo.Adapt<FileInfoDto>());
            if (File.Exists(fileName))
                File.Delete(fileName);

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

        public string GetUrl(FileInfoDto fileInfo) =>
            GetUrl(fileInfo.IdWell, fileInfo.IdCategory, fileInfo.Id, Path.GetExtension(fileInfo.Name));

        public string GetUrl(int idFile)
        {
            var fileInfo = db.Files
                .FirstOrDefault(f => f.Id == idFile);

            if (fileInfo is null)
                return null;

            return GetUrl(fileInfo.IdWell, fileInfo.IdCategory, fileInfo.Id, Path.GetExtension(fileInfo.Name));
        }

        public string GetUrl(int idWell, int idCategory, int idFile, string dotExtention) =>
            Path.Combine(RootPath, idWell.ToString(), idCategory.ToString(), $"{idFile}{dotExtention}");
         
    }
}