using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudApp.Services
{
    /// <summary>
    /// Сервис доступа к файлам
    /// </summary>
    public class FileService
    {
        private readonly IFileRepository fileRepository;
        private readonly IFileStorageRepository fileStorageRepository;

        /// <summary>
        /// Сервис доступа к файлам
        /// </summary>
        /// <param name="fileRepository"></param>
        /// <param name="fileStorageRepository"></param>
        public FileService(IFileRepository fileRepository, IFileStorageRepository fileStorageRepository)
        {
            this.fileRepository = fileRepository;
            this.fileStorageRepository = fileStorageRepository;
        }

        /// <summary>
        /// переместить файл
        /// </summary>
        /// <param name="idWell"></param>
        /// <param name="idUser"></param>
        /// <param name="idCategory"></param>
        /// <param name="destinationFileName"></param>
        /// <param name="srcFilePath"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        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);
            var fileSize = fileStorageRepository.GetFileLength(srcFilePath);

            //save info to db
            var dto = new FileInfoDto {
                IdWell = idWell,
                IdAuthor = idUser,
                IdCategory = idCategory,
                Name = destinationFileName,
                Size = fileSize
            };
            var fileId = await fileRepository.InsertAsync(dto, token)
                .ConfigureAwait(false);
            
            string filePath = fileStorageRepository.MakeFilePath(idWell, idCategory, destinationFileName, fileId);
            fileStorageRepository.MoveFile(srcFilePath, filePath);
            
            return await GetOrDefaultAsync(fileId, token);
        }

        /// <summary>
        /// Сохранить файл
        /// </summary>
        /// <param name="idWell"></param>
        /// <param name="idUser"></param>
        /// <param name="idCategory"></param>
        /// <param name="fileFullName"></param>
        /// <param name="fileStream"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public async Task<FileInfoDto> SaveAsync(int idWell, int? idUser, int idCategory,
            string fileFullName, Stream fileStream, CancellationToken token)
        {
            //save info to db
            var dto = new FileInfoDto
            {
                IdWell = idWell,
                IdAuthor = idUser,
                IdCategory = idCategory,
                Name = Path.GetFileName(fileFullName),
                Size = fileStream.Length
            };

            var fileId = await fileRepository.InsertAsync(dto, token)
                .ConfigureAwait(false);

            //save stream to disk
            string filePath = fileStorageRepository.MakeFilePath(idWell, idCategory, fileFullName, fileId);
            await fileStorageRepository.SaveFileAsync(filePath, fileStream, token);

            return (await GetOrDefaultAsync(fileId, token))!;
        }

        /// <summary>
        /// удалить файл
        /// </summary>
        /// <param name="idFile"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task<int> DeleteAsync(int idFile, CancellationToken token)
            => DeleteAsync(new int[] { idFile }, token);

        /// <summary>
        /// удалить файлы
        /// </summary>
        /// <param name="ids"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public async Task<int> DeleteAsync(IEnumerable<int> ids, CancellationToken token)
        {
            if (ids is null || !ids.Any())
                return 0;

            var files = await fileRepository.DeleteAsync(ids, token).ConfigureAwait(false);

            if (files is null || !files.Any())
                return 0;

            var filesName = files.Select(x => GetUrl(x.IdWell, x.IdCategory, x.Id, Path.GetExtension(x.Name)));
            fileStorageRepository.DeleteFiles(filesName);

            return files.Any() ? 1 : 0;
        }

        /// <summary>
        /// получить путь для скачивания
        /// </summary>
        /// <param name="fileInfo"></param>
        /// <returns></returns>
        public string GetUrl(FileInfoDto fileInfo) =>
            GetUrl(fileInfo.IdWell, fileInfo.IdCategory, fileInfo.Id, Path.GetExtension(fileInfo.Name));

        /// <summary>
        /// получить путь для скачивания
        /// </summary>
        /// <param name="idWell"></param>
        /// <param name="idCategory"></param>
        /// <param name="idFile"></param>
        /// <param name="dotExtention"></param>
        /// <returns></returns>
        public string GetUrl(int idWell, int idCategory, int idFile, string dotExtention) =>
            fileStorageRepository.GetFilePath(idWell, idCategory, idFile, dotExtention);

        /// <summary>
        /// пометить метку файла как удаленную
        /// </summary>
        /// <param name="idMark"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task<int> MarkFileMarkAsDeletedAsync(int idMark,
            CancellationToken token)
        => fileRepository.MarkFileMarkAsDeletedAsync(new int[] { idMark }, token);

        /// <summary>
        /// Инфо о файле
        /// </summary>
        /// <param name="idsFile"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public async Task<IEnumerable<FileInfoDto>> GetInfoByIdsAsync(IEnumerable<int> idsFile, CancellationToken token)
        {
            var result = await fileRepository.GetInfoByIdsAsync(idsFile, token).ConfigureAwait(false);
            return result;
        }

        /// <summary>
        /// Получить файлы определенной категории
        /// </summary>
        /// <param name="request"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task<IEnumerable<FileInfoDto>> GetInfosAsync(FileRequest request, CancellationToken token)
            => fileRepository.GetInfosAsync(request, token);

        /// <summary>
        /// Получить список файлов в контейнере
        /// </summary>
        /// <param name="request"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task<PaginationContainer<FileInfoDto>> GetInfosPaginatedAsync(FileRequest request, CancellationToken token)
            => fileRepository.GetInfosPaginatedAsync(request, token);

        /// <summary>
        /// Пометить файл как удаленный
        /// </summary>
        /// <param name="idFile"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task<int> MarkAsDeletedAsync(int idFile, CancellationToken token = default)
            => fileRepository.MarkAsDeletedAsync(idFile, token);

        /// <summary>
        /// добавить метку на файл
        /// </summary>
        /// <param name="fileMarkDto"></param>
        /// <param name="idUser"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task<int> CreateFileMarkAsync(FileMarkDto fileMarkDto, int idUser, CancellationToken token)
            => fileRepository.CreateFileMarkAsync(fileMarkDto, idUser, token);

        /// <summary>
        /// Получить запись по id
        /// </summary>
        /// <param name="id"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task<FileInfoDto?> GetOrDefaultAsync(int id, CancellationToken token)
            => fileRepository.GetOrDefaultAsync(id, token);

        /// <summary>
        /// получить инфо о файле по метке
        /// </summary>
        /// <param name="idMark"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task<FileInfoDto> GetByMarkId(int idMark, CancellationToken token)
            => fileRepository.GetByMarkId(idMark, token);

        /// <summary>
        /// получить инфо о файле по метке
        /// </summary>
        /// <param name="idsMarks"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task<int> MarkFileMarkAsDeletedAsync(IEnumerable<int> idsMarks, CancellationToken token)
            => fileRepository.MarkFileMarkAsDeletedAsync(idsMarks, token);

        /// <summary>
        /// Удаление всех файлов по скважине помеченных как удаленные
        /// </summary>
        /// <param name="idWell"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public async Task<int> DeleteFilesFromDbMarkedDeletionByIdWell(int idWell, CancellationToken token)
        {
            var files = await fileRepository.GetInfosAsync(
                new FileRequest
                {
                    IdWell = idWell,
                    IsDeleted = true
                },
                token);
            var result = await DeleteAsync(files.Select(x => x.Id), token);
            return result;
        }

        /// <summary>
        /// Удаление всех файлов с диска о которых нет информации в базе
        /// </summary>
        /// <param name="idWell"></param>
        /// <param name="token"></param>
        public async Task<int> DeleteFilesNotExistStorage(int idWell, CancellationToken token)
        {
            var files = await fileRepository.GetInfosAsync(
                new FileRequest
                {
                    IdWell = idWell
                },
                token);
            var result = await Task.FromResult(fileStorageRepository.DeleteFilesNotInList(idWell, files.Select(x => x.Id)));
            return result;
        }

        /// <summary>
        /// Вывод списка всех файлов из базы, для которых нет файла на диске
        /// </summary>
        /// <param name="idWell"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public async Task<IEnumerable<FileInfoDto>> GetListFilesNotDisc(int idWell, CancellationToken token)
        {
            var files = await fileRepository.GetInfosAsync(
                new FileRequest
                {
                    IdWell = idWell
                },
                token);
            var result = fileStorageRepository.GetListFilesNotDisc(files);
            return result;
        }

        /// <summary>
        /// Получить файловый поток по идентификатору файла
        /// </summary>
        /// <param name="fileInfo"></param>
        /// <returns></returns>
        public Stream GetFileStream(FileInfoDto fileInfo)
        {
            var relativePath = GetUrl(fileInfo);
            var fileStream = new FileStream(Path.GetFullPath(relativePath), FileMode.Open);

            return fileStream;
        }
    }
}