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)
                .Include(f => f.FileMarks)
                    .ThenInclude(m => m.User);
        }

        public async Task<string> GetSharedUrlAsync(int idFileInfo, int idUser, IFileShareService fileShareService,
            CancellationToken token)
        {
            var fileInfo = await GetInfoAsync(idFileInfo, token);
            if (fileInfo is null)
                return null;
            var sharedUrl = await GetSharedUrlAsync(fileInfo, idUser, fileShareService, token);
            return sharedUrl;
        }

        public async Task<string> GetSharedUrlAsync( FileInfoDto fileInfo, int idUser, IFileShareService fileShareService,
            CancellationToken token)
        {
            var fileWebUrl = fileInfo.PublishInfo?.WebStorageFileUrl;
            
            if (!string.IsNullOrEmpty(fileWebUrl)) 
                return fileWebUrl;
            
            var relativePath = GetUrl(fileInfo);
            var sharedUrl = await fileShareService.PublishFileToCloudAsync(relativePath, 
                fileInfo.Name, token);

            await SaveWeblinkToFileInfo(fileInfo.Id, idUser, sharedUrl, token);

            return sharedUrl;
        }

        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)
        {
            //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);
                
            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)
        {
            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 &&
                            !e.IsDeleted);

            if (!string.IsNullOrEmpty(companyName))
                query = query.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 idFile,
            CancellationToken token)
        {
            var entity = await dbSetConfigured
                .AsNoTracking()
                .FirstOrDefaultAsync(f => f.Id == idFile, 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(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(FileInfoDto fileInfo) =>
            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}");

        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);
            var dto = entity.Adapt<FileInfoDto>();
            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.Now;
            newFileMark.IdUser = idUser;
                        
            db.FileMarks.Add(newFileMark);
            return await db.SaveChangesAsync(token);
        }
        
        public async Task<int> MarkFileMarkAsDeletedAsync(int idMark, 
            CancellationToken token)
        {
            var fileMark = await db.FileMarks
                .FirstOrDefaultAsync(m => m.Id == idMark, token)
                .ConfigureAwait(false);
            fileMark.IsDeleted = true;
            return await db.SaveChangesAsync(token);
        }        

        private async Task<int> SaveWeblinkToFileInfo(int idFileInfo, int idUser, string weblink,
            CancellationToken token)
        {
            var fileInfo = await db.Files.FirstOrDefaultAsync(f => f.Id == idFileInfo, token)
                .ConfigureAwait(false);
            fileInfo.PublishInfo = new FilePublishInfo()
            {
                IdPublisher = idUser,
                Date = DateTime.Now,
                WebStorageFileUrl = weblink
            };

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