namespace Hims.Infrastructure.Helpers
{
    using System;
    using System.IO;
    using System.Net;
    using System.Threading.Tasks;
    using Domain.Helpers;
    using Hims.Domain.Configurations;
    using Microsoft.AspNetCore.Http;
    using Hims.Shared.DataFilters;

    /// <inheritdoc />
    public class FtpUploadHelper : IFtpUploadHelper
    {
        /// <summary>
        /// The ftp configuration.
        /// </summary>
        private readonly INewFtpConfiguration newftpConfiguration;

        /// <inheritdoc />
        public FtpUploadHelper(INewFtpConfiguration NewftpConfiguration)
        {
            this.newftpConfiguration = NewftpConfiguration;
        }

        /// <inheritdoc />
        public async Task<int> CreateDirectory(string folderNameWithCompletePath)
        {
            try
            {
                FtpWebRequest request = (FtpWebRequest)WebRequest.Create(this.newftpConfiguration.FtpURL + folderNameWithCompletePath);

                request.Method = WebRequestMethods.Ftp.MakeDirectory;
                request.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);
                request.UsePassive = true;
                request.UseBinary = true;
                request.KeepAlive = true;
                FtpWebResponse response = (FtpWebResponse)await request.GetResponseAsync();
                var statuscode = (int)response.StatusCode;
                response.Close();
                return statuscode;
            }
            catch (WebException ex)
            {
                FtpWebResponse response = (FtpWebResponse)ex.Response;
                if (response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable)
                {
                    response.Close();
                    return 1;
                }
                else
                {
                    response.Close();
                    return 0;
                }
            }

        }

        /// <inheritdoc />
        public async Task<int> UploadProfileImageAsync(string base64Image, string filePath)
        {
            if (string.IsNullOrEmpty(filePath) && string.IsNullOrEmpty(base64Image))
            {
                return 0;
            }

            var imageBytes = CoreFilter.ToImageBytes(base64Image);
            var imageStream = new MemoryStream(imageBytes);
            var uploadResponse = await this.UploadFileWithStreamAsync(filePath, imageStream);
            return uploadResponse;
        }

        /// <inheritdoc />
        public async Task<int> UploadFileWithByteArrayAsync(string fileName, byte[] source)
        {
            if (source == null)
            {
                return -1;
            }
            else if (string.IsNullOrWhiteSpace(fileName))
            {
                return -1;
            }
            FtpWebRequest ftp = (FtpWebRequest)WebRequest.Create(this.newftpConfiguration.FtpURL + fileName);
            ftp.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);
            ftp.KeepAlive = true;
            ftp.UseBinary = true;
            ftp.UsePassive = true;
            ftp.Method = WebRequestMethods.Ftp.UploadFile;
            Stream ftpstream = ftp.GetRequestStream();
            int bufferSize = 2048; // Byte on single go
            int pointer = 0;
            while (source.Length > (bufferSize * pointer))
            {
                bufferSize = (source.Length < (bufferSize * (pointer + 1))) ? source.Length - (bufferSize * (pointer)) : bufferSize;
                await ftpstream.WriteAsync(source, pointer, bufferSize);
                pointer++; // Pointing to next Byte blocks
            }
            ftpstream.Close();
            FtpWebResponse response = (FtpWebResponse)await ftp.GetResponseAsync();
            var statuscode = (int)response.StatusCode;
            response.Close();
            return statuscode;
        }

        public async Task<int> UploadFileWithStreamAsync(string fileName, Stream stream)
        {
            if (stream == null)
            {
                return -1;
            }
            else if (string.IsNullOrWhiteSpace(fileName))
            {
                return -1;
            }
            FtpWebRequest ftp = (FtpWebRequest)WebRequest.Create(this.newftpConfiguration.FtpURL + fileName);
            ftp.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);
            ftp.KeepAlive = true;
            ftp.UseBinary = true;
            ftp.UsePassive = true;
            ftp.Method = WebRequestMethods.Ftp.UploadFile;
            Stream ftpstream = ftp.GetRequestStream();

            byte[] buffer = new byte[2048];
            int bytesRead;
            while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                await ftpstream.WriteAsync(buffer, 0, bytesRead);
            }
            ftpstream.Close();
            FtpWebResponse response = (FtpWebResponse)await ftp.GetResponseAsync();
            var statuscode = (int)response.StatusCode;
            response.Close();
            return statuscode;
        }

        /// <inheritdoc />
        public async Task<int> UploadFromFileAsync(string fileName, IFormFile fileInfo)
        {
            if (fileInfo == null)
            {
                return -1;
            }
            else if (string.IsNullOrWhiteSpace(fileName))
            {
                return -1;
            }
            FtpWebRequest ftp = (FtpWebRequest)WebRequest.Create(this.newftpConfiguration.FtpURL + fileName);
            ftp.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);
            ftp.KeepAlive = true;
            ftp.UseBinary = true;
            ftp.UsePassive = true;
            ftp.Method = WebRequestMethods.Ftp.UploadFile;
            using (Stream fileStream = fileInfo.OpenReadStream())
            using (Stream ftpStream = ftp.GetRequestStream())
            {
                await fileStream.CopyToAsync(ftpStream);
                ftpStream.Close();
            }
            FtpWebResponse response = (FtpWebResponse)await ftp.GetResponseAsync();
            var statuscode = (int)response.StatusCode;
            response.Close();
            return statuscode;
        }

        /// <inheritdoc />
        public async Task<int> DeleteFile(string fileNameWithDestinationPath)
        {
            try
            {
                FtpWebRequest ftp = (FtpWebRequest)WebRequest.Create(this.newftpConfiguration.FtpURL + fileNameWithDestinationPath);
                ftp.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);
                ftp.KeepAlive = true;
                ftp.UseBinary = true;
                ftp.UsePassive = true;
                ftp.Method = WebRequestMethods.Ftp.DeleteFile;
                FtpWebResponse response = (FtpWebResponse)await ftp.GetResponseAsync();
                var statuscode = (int)response.StatusCode;
                response.Close();
                return statuscode;

            }
            catch (WebException ex)
            {
                FtpWebResponse response = (FtpWebResponse)ex.Response;
                if (response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable)
                {
                    response.Close();
                    return 1;
                }
                else
                {
                    response.Close();
                    return 0;
                }
            }
        }

        /// <inheritdoc />
        public async Task<byte[]> DownloadSmallFilesAsync(string fileNameWithPath)
        {
            int bytesRead;
            byte[] buffer = new byte[2048];
            byte[] fileData = null;
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(this.newftpConfiguration.FtpURL + fileNameWithPath);
            request.Method = WebRequestMethods.Ftp.DownloadFile;
            request.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);

            Stream reader = request.GetResponse().GetResponseStream();
            using (MemoryStream ms = new MemoryStream())
            {
                while (true)
                {
                    bytesRead = await reader.ReadAsync(buffer, 0, buffer.Length);
                    if (bytesRead == 0)
                        break;
                    ms.Write(buffer, 0, bytesRead);
                }
                fileData = ms.ToArray();
            }
            reader.Close();

            return fileData;
        }

        /// <inheritdoc />
        public async Task<Stream> DownloadLargeFile(string fileNameWithPath)
        {
            var read = await Task.Run(() =>
            {
                FtpWebRequest request = (FtpWebRequest)WebRequest.Create(this.newftpConfiguration.FtpURL + fileNameWithPath);
                request.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);
                request.Method = WebRequestMethods.Ftp.DownloadFile;
                Stream reader = request.GetResponse().GetResponseStream();
                return reader;
            });
            return read;
        }

        /// <inheritdoc />
        public async Task<string> DownloadBase64DataAsync(string fileNameWithPath)
        {
            int bytesRead;
            byte[] buffer = new byte[2048];
            byte[] fileData = null;
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(this.newftpConfiguration.FtpURL + fileNameWithPath);
            request.Method = WebRequestMethods.Ftp.DownloadFile;
            request.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);

            Stream reader = request.GetResponse().GetResponseStream();
            using (MemoryStream ms = new MemoryStream())
            {
                while (true)
                {
                    bytesRead = await reader.ReadAsync(buffer, 0, buffer.Length);
                    if (bytesRead == 0)
                        break;
                    ms.Write(buffer, 0, bytesRead);
                }
                fileData = ms.ToArray();
            }
            reader.Close();

            var base64 = Convert.ToBase64String(fileData);
            return base64;
        }

        /// <inheritdoc />
        public bool CheckIfFileExistsOnServer(string fileName)
        {
            var request = (FtpWebRequest)WebRequest.Create(this.newftpConfiguration.FtpURL + fileName);
            request.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);
            request.Method = WebRequestMethods.Ftp.GetFileSize;
            request.KeepAlive = true;
            request.UseBinary = true;
            request.UsePassive = true;
            try
            {
                var response = (FtpWebResponse)request.GetResponse();
                response.Close();
                return true;
            }
            catch (WebException ex)
            {
                FtpWebResponse response = (FtpWebResponse)ex.Response;
                if (response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable)
                {
                    response.Close();
                    return false;
                }
                response.Close();
            }
            return false;
        }

        public async Task<int> DeleteDirectory(string folder)
        {
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(this.newftpConfiguration.FtpURL + folder);
            request.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);
            request.Method = WebRequestMethods.Ftp.RemoveDirectory;
            request.KeepAlive = false;
            request.UseBinary = true;
            request.UsePassive = true;
            FtpWebResponse response = (FtpWebResponse)request.GetResponse();
            return (int)response.StatusCode;
        }

        public async Task<bool> Rename(string source, string destination)
        {
            Uri serverFile = new Uri(this.newftpConfiguration.FtpURL + source);
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(serverFile);
            request.Method = WebRequestMethods.Ftp.Rename;
            request.UseBinary = true;
            request.Credentials = new NetworkCredential(this.newftpConfiguration.Username, this.newftpConfiguration.Password);
            request.RenameTo = "/"+destination;
            FtpWebResponse response = (FtpWebResponse)request.GetResponse();
            var success = response.StatusCode == FtpStatusCode.CommandOK || response.StatusCode == FtpStatusCode.FileActionOK;
            return success;
        }
    }
}