using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using System.Collections.Generic;
using System.Linq;
using AyaNova.Models;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Util;
using AyaNova.Biz;


namespace AyaNova.Api.ControllerHelpers
{


    //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
    //See AttachmentController at bottom of class for example of form that works with this code

    /// <summary>
    /// Handle processing uplod form with potentially huge files being uploaded (which means can't use simplest built in upload handler method)
    /// </summary>
    internal static class ApiUploadProcessor
    {



        /// <summary>
        /// Process uploaded attachment file
        /// Will be treated as a temporary file for further processing into database
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        internal static async Task<ApiUploadedFilesResult> ProcessAttachmentUpload(Microsoft.AspNetCore.Http.HttpContext httpContext)
        {
            return await ProcessUpload(httpContext, true);
        }


        /// <summary>
        /// Process uploaded utility file (backup, import etc)
        /// Anything that will be stored in the backup folder as is
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        internal static async Task<ApiUploadedFilesResult> ProcessUtilityFileUpload(Microsoft.AspNetCore.Http.HttpContext httpContext)
        {
            return await ProcessUpload(httpContext, false);
        }

        /// <summary>
        /// handle upload
        /// </summary>
        /// <param name="httpContext"></param>
        /// <param name="processAsAttachment"></param>
        /// <returns><see cref="ApiUploadedFilesResult"/> list of files and form field data (if present)</returns>
        private static async Task<ApiUploadedFilesResult> ProcessUpload(Microsoft.AspNetCore.Http.HttpContext httpContext, bool processAsAttachment)
        {

            ApiUploadedFilesResult result = new ApiUploadedFilesResult();
            FormOptions _defaultFormOptions = new FormOptions();

            // Used to accumulate all the form url encoded key value pairs in the 
            // request.
            var formAccumulator = new KeyValueAccumulator();

            var boundary = MultipartRequestHelper.GetBoundary(
                MediaTypeHeaderValue.Parse(httpContext.Request.ContentType),
                _defaultFormOptions.MultipartBoundaryLengthLimit);
            var reader = new MultipartReader(boundary, httpContext.Request.Body);

            var section = await reader.ReadNextSectionAsync();


            while (section != null)
            {
                ContentDispositionHeaderValue contentDisposition;
                var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);

                if (hasContentDispositionHeader)
                {
                    if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
                    {

                        string filePathAndName = string.Empty;
                        var CleanedUploadFileName = contentDisposition.FileName.Value.Replace("\"", "");

                        if (processAsAttachment)
                        {
                            //get temp file path and temp file name
                            filePathAndName = FileUtil.NewRandomAttachmentFileName;
                        }
                        else
                        {
                            //store directly into the backup file folder
                            //NOTE: all utility files are always stored as lowercase to avoid recognition issues down the road
                            CleanedUploadFileName = CleanedUploadFileName.ToLowerInvariant();
                            filePathAndName = FileUtil.GetFullPathForUtilityFile(CleanedUploadFileName);
                        }


                        //save to disk
                        using (var stream = new FileStream(filePathAndName, FileMode.Create))
                        {
                            section.Body.CopyTo(stream);
                        }
                        result.UploadedFiles.Add(new UploadedFileInfo()
                        {
                            InitialUploadedPathName = filePathAndName,
                            OriginalFileName = CleanedUploadFileName,
                            MimeType = section.ContentType

                        });
                    }
                    else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
                    {
                        // Content-Disposition: form-data; name="key"
                        //
                        // value

                        // Do not limit the key name length here because the 
                        // multipart headers length limit is already in effect.
                        var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
                        var encoding = GetEncoding(section);
                        using (var streamReader = new StreamReader(
                            section.Body,
                            encoding,
                            detectEncodingFromByteOrderMarks: true,
                            bufferSize: 1024,
                            leaveOpen: true))
                        {
                            // The value length limit is enforced by MultipartBodyLengthLimit
                            var value = await streamReader.ReadToEndAsync();
                            if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
                            {
                                value = String.Empty;
                            }
                            formAccumulator.Append(key.Value, value);

                            if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
                            {
                                throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
                            }
                        }
                    }
                }

                // Drains any remaining section body that has not been consumed and
                // reads the headers for the next section.
                section = await reader.ReadNextSectionAsync();
            }

            //Get any extra form fields and return them
            result.FormFieldData = formAccumulator.GetResults();
            return result;

        }


        private static Encoding GetEncoding(MultipartSection section)
        {
            MediaTypeHeaderValue mediaType;
            var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
            // UTF-7 is insecure and should not be honored. UTF-8 will succeed in 
            // most cases.
            if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
            {
                return Encoding.UTF8;
            }
            return mediaType.Encoding;
        }




        /// <summary>
        /// Contains result of upload form processor
        /// </summary>
        public class ApiUploadedFilesResult
        {
            public Dictionary<string, Microsoft.Extensions.Primitives.StringValues> FormFieldData { get; set; }
            public List<UploadedFileInfo> UploadedFiles { get; set; }

            public ApiUploadedFilesResult()
            {
                FormFieldData = new Dictionary<string, Microsoft.Extensions.Primitives.StringValues>();
                UploadedFiles = new List<UploadedFileInfo>();
            }

        }




    }//eoc


}//eons
