using System;
using System.IO;
using System.IO.Abstractions;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using BO.AppServer.Business.Exceptions;
using BO.AppServer.Data.Entity;
using BO.AppServer.Data.Repository;
using BO.AppServer.Metadata.Configuration;
using BO.AppServer.Metadata.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Quadarax.Foundation.Core.Business;
using Quadarax.Foundation.Core.Exceptions;
using Quadarax.Foundation.Core.Value.Extensions;

namespace BO.AppServer.Business.Services
{
    public class StorageService : AbstractService
    {
        #region *** Private fields ***
        private IFileSystem _fileSystem;
        private IOptions<Configuration> _config;
        private ArtifactRepo _repoArtifact;
        #endregion
        #region *** Constructors ***
        public StorageService(ArtifactRepo repoArtifact,
            IOptions<Configuration> config, 
            IFileSystem fileSystem, 
            IPrincipal currentPrincipal, 
            ILoggerFactory logger) : base(currentPrincipal, logger)
        {
            _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
            _config = config ?? throw new ArgumentNullException(nameof(config));
            _repoArtifact = repoArtifact ?? throw new ArgumentNullException(nameof(repoArtifact));

            if (!_fileSystem.Directory.Exists(_config.Value.System.Storage.InputPath.EnsurePathNonBackslash()))
            {
                _fileSystem.Directory.CreateDirectory(_config.Value.System.Storage.InputPath.EnsurePathNonBackslash());
                Log.LogInformation($"Storage Input directory '{_config.Value.System.Storage.InputPath.EnsurePathNonBackslash()}' created.");
            }
            else
                Log.LogInformation($"Storage Input directory '{_config.Value.System.Storage.InputPath.EnsurePathNonBackslash()}' verified.");
            if (!_fileSystem.Directory.Exists(_config.Value.System.Storage.OutputPath.EnsurePathNonBackslash()))
            {
                _fileSystem.Directory.CreateDirectory(_config.Value.System.Storage.OutputPath.EnsurePathNonBackslash());
                Log.LogInformation($"Storage Output directory '{_config.Value.System.Storage.OutputPath.EnsurePathNonBackslash()}' created.");
            }
            else
                Log.LogInformation($"Storage Input directory '{_config.Value.System.Storage.InputPath.EnsurePathNonBackslash()}' verified.");

        }
        #endregion
        #region *** Public Operations ***

        public async Task<long> PushContentAsync(long artifactId, Stream fileData)
        {

            var artifact = _repoArtifact.Get(artifactId);
            if (artifact == null)
            {
                var exc = new EntityNotExistsException(typeof(Artifact),nameof(Artifact.Id), artifactId);
                Log.LogWarning(exc.Message);
                throw exc;
            }

            var documentId = artifact.Metadocument.Id;
            var workspaceId = artifact.Metadocument.Structure.WorkspaceId;
            var fileName = GetFileName(artifactId, artifact.Name, artifact.Extension);
            var artType = (ArtifactTypeEnum)artifact.Type;
            var fullFileName = EnureDirectory(workspaceId, documentId, artType) + _fileSystem.Path.DirectorySeparatorChar + fileName;
            long size;
            await using (var fs = _fileSystem.File.Create(fullFileName, _config.Value.System.Storage.IoBufferSize, FileOptions.Asynchronous))
            {
                fileData.Position = 0;
                await fileData.CopyToAsync(fs, _config.Value.System.Storage.IoBufferSize);
                await fs.FlushAsync();
                size = fs.Length;
                fs.Close();
            }

            return size;
        }

        public async Task<Tuple<Stream, long>> PopContentAsync(long artifactId)
        {
            var artifact = _repoArtifact.Get(artifactId);
            if (artifact == null)
            {
                var exc = new EntityNotExistsException(typeof(Artifact),nameof(Artifact.Id), artifactId);
                Log.LogWarning(exc.Message);
                throw exc;
            }

            var documentId = artifact.Metadocument.Id;
            var workspaceId = artifact.Metadocument.Structure.WorkspaceId;
            var fileName = GetFileName(artifactId, artifact.Name, artifact.Extension);
            var artType = (ArtifactTypeEnum)artifact.Type;
            var fullFileName = EnureDirectory(workspaceId, documentId, artType) + _fileSystem.Path.DirectorySeparatorChar + fileName;

            if (!_fileSystem.File.Exists(fullFileName))
            {
                var message = $"Content for artifact '{fullFileName}' [{artifactId}] not found!";
                Log.LogWarning(message);
                throw new CodeException(1300, message);
            }


            var buffer = new byte[_config.Value.System.Storage.IoBufferSize];

            //var outputStream = new MemoryStream(buffer);
            var outputStream = new MemoryStream();

            await using (var fs = _fileSystem.File.OpenRead(fullFileName))
            {
                //await fs.CopyToAsync(outputStream, _config.Value.System.Storage.IoBufferSize);
                await fs.CopyToAsync(outputStream);
                await outputStream.FlushAsync();
                fs.Close();
            }

            outputStream.Seek(0, SeekOrigin.Begin);
            return new Tuple<Stream, long>(outputStream, outputStream.Length);
        }

        public async Task<bool> DeleteContent(long artifactId)
        {
            var artifact = _repoArtifact.Get(artifactId);
            if (artifact == null)
            {
                var exc = new EntityNotExistsException(typeof(Artifact),nameof(Artifact.Id), artifactId);
                Log.LogWarning(exc.Message);
                throw exc;
            }

            var documentId = artifact.Metadocument.Id;
            var workspaceId = artifact.Metadocument.Structure.WorkspaceId;
            var fileName = GetFileName(artifactId, artifact.Name, artifact.Extension);
            var artType = (ArtifactTypeEnum)artifact.Type;
            var fullFileName = EnureDirectory(workspaceId, documentId, artType) + _fileSystem.Path.DirectorySeparatorChar + fileName;

            if (!_fileSystem.File.Exists(fullFileName))
            {
                var message = $"Content for artifact '{fullFileName}' [{artifactId}] not found for delete!";
                Log.LogInformation(message);
                return false;
            }

            _fileSystem.File.Delete(fullFileName);

            return true;
        }

        #endregion


        #region *** Private Operations ***

        private string EnureDirectory(long workspaceId, long documentId, ArtifactTypeEnum type)
        {
            var dirName = GetDirectoryName(workspaceId, documentId, type);
            if (!_fileSystem.Directory.Exists(dirName))
            {
                _fileSystem.Directory.CreateDirectory(dirName);
                Log.LogInformation($"Storage document directory '{dirName}' created.");
            }

            return dirName;
        }

        private string GetDirectoryName(long workspaceId, long documentId, ArtifactTypeEnum type)
        {
            var sb = new StringBuilder();
            sb.Append(type == ArtifactTypeEnum.Input ? _config.Value.System.Storage.InputPath.EnsurePathNonBackslash() : _config.Value.System.Storage.OutputPath.EnsurePathNonBackslash()).Append(_fileSystem.Path.DirectorySeparatorChar);
            sb.Append(workspaceId).Append(_fileSystem.Path.DirectorySeparatorChar);
            sb.Append(documentId);
            return sb.ToString();
        }
        private string GetFileName(long artifactId, string fileNameWoExtension, string extension)
        {
            var sb = new StringBuilder();
            sb.Append(fileNameWoExtension).Append(".");
            sb.Append(artifactId).Append(".");
            sb.Append(extension);
            return sb.ToString();
        }
        #endregion
    }
}
