using BO.ProcessServer.Options;
using Quadarax.Foundation.Core.Object;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using Quadarax.Foundation.Core.IO.Extensions;
using Quadarax.Foundation.Core.Logging;

// ReSharper disable once InconsistentNaming
namespace BO.ProcessServer.Business
{
    internal class Pool : DisposableObject
    {
        #region *** Enums ***
        public enum PoolTypeEnum
        {
            Pending,
            Working,
            Done,
            Fail
        }
        #endregion

        #region *** Constants ***
        private const string CS_ALL_FILES = "*.*";
        #endregion

        #region *** Private fields ***
        private readonly IFileSystem _fileSystem;
        private IDirectoryInfo _dirPending;
        private IDirectoryInfo _dirWorking;
        private IDirectoryInfo _dirDone;
        private IDirectoryInfo _dirFail;

        private ILog _log;
        #endregion

        #region *** Constructors ***
        public Pool(Configuration configuration, IFileSystem fileSystem, ILogger logger)
        {
            _log = logger.GetLogger(GetType());
            if (configuration==null) throw new ArgumentNullException(nameof(configuration));

            _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
            var basePath = TranslatePath(configuration.System.PoolPathBase);
            if (!_fileSystem.Directory.Exists(TranslatePath(configuration.System.PoolPathBase)))
                throw new FileNotFoundException($"BasePool directory '{basePath}' not found!");

            _dirPending = _fileSystem.DirectoryInfo.FromDirectoryName(_fileSystem.Path.Combine(basePath, configuration.System.PoolPathPending));
            _dirWorking = _fileSystem.DirectoryInfo.FromDirectoryName(_fileSystem.Path.Combine(basePath, configuration.System.PoolPathWorking));
            _dirDone = _fileSystem.DirectoryInfo.FromDirectoryName(_fileSystem.Path.Combine(basePath, configuration.System.PoolPathDone));
            _dirFail = _fileSystem.DirectoryInfo.FromDirectoryName(_fileSystem.Path.Combine(basePath, configuration.System.PoolPathFail));

            _log.Log(LogSeverityEnum.Info, $"Open pool in '{basePath}'. Initial status (files): Pending={_dirPending.GetFiles().Length};Working={_dirWorking.GetFiles().Length};Done={_dirDone.GetFiles().Length};Fail={_dirFail.GetFiles().Length}");
        }
        #endregion

        #region *** Public Operations ***
        public async Task<string> PutDocumentAsync(long documentId, string fileName, Stream streamToWrite)
        {
            var poolFileName = TranslateFileName(documentId, fileName);
            if (_dirPending.ExistsFile(poolFileName))
            {
                _log.Log(LogSeverityEnum.Warn, $"Pool [Pending]: File '{poolFileName}' already exists. Overwrite.");
                _dirPending.DeleteFile(poolFileName);
            }

            var fullFileName = _dirPending.GetFullFileName(poolFileName);
            await using (var sw = _fileSystem.File.OpenWrite(fullFileName))
            {
                streamToWrite.Seek(0, SeekOrigin.Begin);
                await streamToWrite.CopyToAsync(sw);
                await sw.FlushAsync();
                _log.Log(LogSeverityEnum.Debug, $"Pool [Pending]: File '{poolFileName}' created.");

            }
            return fullFileName;
        }

        public async Task<IEnumerable<string>> GetSinglePendingDocumentFilesAsync()
        {
            var first = GetPoolBranch(PoolTypeEnum.Pending).EnumerateFiles(CS_ALL_FILES, SearchOption.TopDirectoryOnly).FirstOrDefault();
            if (first == null) return new List<string>();

            var documentId = GetDocumentIdFromFileName(first.Name);
            return GetDocumentFiles(PoolTypeEnum.Pending, documentId);
        }

        public long GetDocumentIdFromFileName(string fileName)
        {
            var fileNameOnly = _fileSystem.Path.GetFileName(fileName);
            var pos = fileNameOnly.IndexOf("_", StringComparison.InvariantCulture);
            if (pos < 0)
                throw new ArgumentOutOfRangeException(nameof(fileName),
                    $"FileName '{fileName}' format is unexpected (00000000_name.ext expects).");

            return long.Parse(fileNameOnly.Substring(0, pos));
        }

        public IEnumerable<string> GetDocumentsCount(PoolTypeEnum poolBranchFrom)
        {
            return GetPoolBranch(poolBranchFrom).GetFiles(CS_ALL_FILES, SearchOption.TopDirectoryOnly).Select(x=>x.FullName);
        }

        public IEnumerable<string> GetDocumentFiles(PoolTypeEnum poolBranchFrom, long documentId)
        {
            return GetPoolBranch(poolBranchFrom).GetFiles(TranslateSearchPattern(documentId), SearchOption.TopDirectoryOnly).Select(x=>x.FullName);
        }

        public IEnumerable<string> MoveDocumentFiles(PoolTypeEnum poolBranchFrom, PoolTypeEnum poolBranchTo, long documentId)
        {
            var files = GetDocumentFiles(poolBranchFrom, documentId).ToArray();
            var tasks = new List<Task>();
            foreach (var file in files)
            {
                tasks.Add(Task.Factory.StartNew(() =>
                    {
                        _fileSystem.File.Move(file, GetPoolBranch(poolBranchTo).GetFullFileName(_fileSystem.Path.GetFileName(file)));
                        _log.Log(LogSeverityEnum.Debug, $"Pool [{poolBranchFrom}]: File '{_fileSystem.Path.GetFileName(file)}' moved to [{poolBranchTo}].");
                    }
                ));
            }

            Task.WaitAll(tasks.ToArray());
            return files;
        }

        public IEnumerable<string> DeleteDocumentFiles(PoolTypeEnum poolBranchFrom, long documentId)
        {
            var files = GetPoolBranch(poolBranchFrom).GetFiles(TranslateSearchPattern(documentId), SearchOption.TopDirectoryOnly);
            foreach (var file in files)
            {
                file.Delete();
                _log.Log(LogSeverityEnum.Debug, $"Pool [{poolBranchFrom}]: File '{file.Name}' deleted.");
            }

            return files.Select(x => x.FullName);
        }
        #endregion


        #region *** Private operations & overrides ***

        protected override void OnDisposing()
        {
            _dirPending = null;
            _dirWorking  = null;
            _dirDone  = null;
            _dirFail  = null;
        }

        private string TranslatePath(string path)
        {
            if (string.IsNullOrEmpty(path))
                return path;
            return path.Replace("{CD}", _fileSystem.Directory.GetCurrentDirectory());
        }

        private string TranslateFileName(long documentId, string originalFileName)
        {
            return $"{documentId:D8}_{originalFileName}";
        }

        private string TranslateSearchPattern(long documentId)
        {
            return $"{documentId:D8}_{CS_ALL_FILES}";
        }

        private IDirectoryInfo GetPoolBranch(PoolTypeEnum poolBranch)
        {
            switch (poolBranch)
            {
                case PoolTypeEnum.Done:
                    return _dirDone;
                case PoolTypeEnum.Fail:
                    return _dirFail;
                case PoolTypeEnum.Pending:
                    return _dirPending;
                case PoolTypeEnum.Working:
                    return _dirWorking;
                default:
                    throw new NotSupportedException();
            }
        }
        #endregion
    }
}
