using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using BO.AppServer.Metadata.Dto;
using BO.AppServer.Metadata.Enums;
using BO.ProcessServer.Options;
using Connector.PS;
using Quadarax.Foundation.Core.IO.MimeTypes;
using Quadarax.Foundation.Core.Object;
using Quadarax.Foundation.Core.Value.Extensions;

namespace BO.ProcessServer.Business
{
    internal class ConnectionProxy : DisposableObject, IConnectionPS
    {


        #region *** Private fields ***
        private Configuration _configuration;
        private Random _rand;
        private IList<DocumentRDto> _documentsCache;
        private IFileSystem _fileSystem;
        private long _currentDocId;
        private long _currentArtId;
        #endregion

        #region *** Properties ***
        public bool IsOpen { get; private set; }
        #endregion


        #region *** Constructor ***
        public ConnectionProxy(Configuration configuration, IFileSystem fileSystem)
        {
            _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
            _rand = new Random();
            _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
            _documentsCache = new List<DocumentRDto>();
            _currentDocId = _rand.Next(1, 1000);
            _currentArtId = _rand.Next(1, 10) * _currentDocId;
        }
        #endregion
        

        #region *** Public Operations ***        
        public async Task<IEnumerable<DocumentRDto>> GetDocumentsAsync(DocumentsScopeEnums scope)
        {
            if (!(scope == DocumentsScopeEnums.Pending || scope == DocumentsScopeEnums.Working))
                throw new ArgumentOutOfRangeException(
                    $"Document list scope value must be '{DocumentsScopeEnums.Working}' or '{DocumentsScopeEnums.Pending}' int this context.");

            if (_documentsCache.Count(x=>x.Status == DocumentStatusEnum.New) + _configuration.System.Proxy.App.DefaultDocumentsCount < _configuration.System.Proxy.App.MaxDocumentsCount)
                GenerateDocuments(DocumentStatusEnum.New);
            
            var documents = _documentsCache.Where(x => x.Status == DocumentStatusEnum.New)
                .Take(_configuration.System.PoolPullDocuments).ToArray();
            foreach (var document in documents)
                document.Status = DocumentStatusEnum.Pending;

            return documents;
        }

        public async Task<DocumentStatusEnum> GetDocumentStateAsync(long documentId)
        {
            return _documentsCache.Where(x => x.Id == documentId).Select(x=>x.Status).First();
        }

        public async Task<DocumentRDto> GetDocumentInfoAsync(long documentId)
        {
            return _documentsCache.First(x => x.Id == documentId);
        }

        public async Task<DocumentStatusEnum> SetDocumentStateAsync(long documentId, DocumentStatusEnum status)
        {
            if (!(status == DocumentStatusEnum.New || status == DocumentStatusEnum.Processing || status == DocumentStatusEnum.DoneOk || status == DocumentStatusEnum.DoneFail))
                throw new ArgumentOutOfRangeException(
                    $"Document status value must be '{DocumentStatusEnum.New}' or '{DocumentStatusEnum.Processing}' or '{DocumentStatusEnum.DoneOk}' or '{DocumentStatusEnum.DoneFail}' in this context.");


            var document = _documentsCache.First(x => x.Id == documentId);
            document.Status = status;
            return status;
        }

        public async Task<Stream> GetDocumentContentAsync(long documentId, long artifactId)
        {
            var sw = new StreamWriter(new MemoryStream());
            await sw.WriteLineAsync("This is a mock data");
            await sw.FlushAsync();
            sw.BaseStream.Seek(0, SeekOrigin.Begin);
            return sw.BaseStream;
        }

        public async Task<ArtifactRDto> SetDocumentContentAsync(long documentId, string fileName, string mimeType, bool overwriteIfExists, Stream contentStream)
        {
            var document = _documentsCache.First(x => x.Id == documentId);
            var artifacts = new List<ArtifactRDto>(document.Artifacts);
            _currentArtId++;
            var artifact = new ArtifactRDto()
            {
                Length = contentStream.Length,
                Type = ArtifactTypeEnum.Output,
                MimeType = mimeType,
                Extension = _fileSystem.Path.GetExtension(fileName),
                Id = _currentArtId
            };
            artifacts.Add(artifact);
            document.Artifacts = artifacts;
            return artifact;
        }

        public void Open(string userName, string userPassword, string magicKey)
        {
            IsOpen = true;
        }

        public void Close()
        {
            IsOpen = false;
        }

        public bool Ping(out TimeSpan duration, out string status)
        {
            duration = TimeSpan.FromMilliseconds(_rand.Next(2000));
            status = "OK";
            return true;
        }
        #endregion

        #region *** Private Operations & Overrides ***
        protected override void OnDisposing()
        {
            _configuration = null;
        }

        protected void GenerateDocuments(DocumentStatusEnum state)
        {
            for (int a = 0; a < _configuration.System.Proxy.App.DefaultDocumentsCount; a++)
            {
                var artifacts = new List<ArtifactRDto>();
                for (int i = 0; i < _configuration.System.Proxy.App.DefaultArtifactCount; i++)
                {
                    var ext = _configuration.System.Proxy.App.Extension.GetRandom();
                    var artifact = new ArtifactRDto()
                    {
                        Id = _currentArtId++,
                        Name = $"{_configuration.System.Proxy.App.NameProfix.GetRandom()}_{_currentArtId:D8}",
                        Extension = ext,
                        MimeType = MimeTypeUtil.GetMimeType(ext),
                        Type = ArtifactTypeEnum.Input
                    };
                    artifacts.Add(artifact);
                }

                var document = new DocumentRDto()
                {
                    Name = $"Doc{_currentDocId++:D8}",
                    Id = _currentDocId,
                    PresetCreditCost = _configuration.System.Proxy.App.PresetCreditCost.GetRandom(),
                    PresetQuality = _configuration.System.Proxy.App.PresetQuality.GetRandom(),
                    PresetProcessProfile = _configuration.System.Proxy.App.PresetProcessProfile.GetRandom(),
                    PresetWatermark = _configuration.System.Proxy.App.PresetWatermark.GetRandom(),
                    PresetTest = _configuration.System.Proxy.App.PresetTest.GetRandom(),
                    Status = state,
                    Artifacts = artifacts
                };
                _documentsCache.Add(document);
            }

            var toDelete = _documentsCache.Where(x =>
                x.Status == DocumentStatusEnum.DoneFail || x.Status == DocumentStatusEnum.DoneOk ||
                x.Status == DocumentStatusEnum.Downloaded || x.Status == DocumentStatusEnum.Expired).ToArray();
            foreach (var del in toDelete)
                _documentsCache.Remove(del);

        }

        #endregion
    }
}
