using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Emby.Server.Implementations.Services;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using IRequest = MediaBrowser.Model.Services.IRequest;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
namespace Emby.Server.Implementations.HttpServer
{
    /// 
    /// Class HttpResultFactory
    /// 
    public class HttpResultFactory : IHttpResultFactory
    {
        /// 
        /// The _logger
        /// 
        private readonly ILogger _logger;
        private readonly IFileSystem _fileSystem;
        private readonly IJsonSerializer _jsonSerializer;
        private IBrotliCompressor _brotliCompressor;
        /// 
        /// Initializes a new instance of the  class.
        /// 
        public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IBrotliCompressor brotliCompressor)
        {
            _fileSystem = fileSystem;
            _jsonSerializer = jsonSerializer;
            _brotliCompressor = brotliCompressor;
            _logger = loggerfactory.CreateLogger("HttpResultFactory");
        }
        /// 
        /// Gets the result.
        /// 
        /// The content.
        /// Type of the content.
        /// The response headers.
        /// System.Object.
        public object GetResult(IRequest requestContext, byte[] content, string contentType, IDictionary responseHeaders = null)
        {
            return GetHttpResult(requestContext, content, contentType, true, responseHeaders);
        }
        public object GetResult(string content, string contentType, IDictionary responseHeaders = null)
        {
            return GetHttpResult(null, content, contentType, true, responseHeaders);
        }
        public object GetResult(IRequest requestContext, Stream content, string contentType, IDictionary responseHeaders = null)
        {
            return GetHttpResult(requestContext, content, contentType, true, responseHeaders);
        }
        public object GetResult(IRequest requestContext, string content, string contentType, IDictionary responseHeaders = null)
        {
            return GetHttpResult(requestContext, content, contentType, true, responseHeaders);
        }
        public object GetRedirectResult(string url)
        {
            var responseHeaders = new Dictionary();
            responseHeaders["Location"] = url;
            var result = new HttpResult(Array.Empty(), "text/plain", HttpStatusCode.Redirect);
            AddResponseHeaders(result, responseHeaders);
            return result;
        }
        /// 
        /// Gets the HTTP result.
        /// 
        private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary responseHeaders = null)
        {
            var result = new StreamWriter(content, contentType, _logger);
            if (responseHeaders == null)
            {
                responseHeaders = new Dictionary();
            }
            if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
            {
                responseHeaders["Expires"] = "-1";
            }
            AddResponseHeaders(result, responseHeaders);
            return result;
        }
        /// 
        /// Gets the HTTP result.
        /// 
        private IHasHeaders GetHttpResult(IRequest requestContext, byte[] content, string contentType, bool addCachePrevention, IDictionary responseHeaders = null)
        {
            string compressionType = null;
            bool isHeadRequest = false;
            if (requestContext != null)
            {
                compressionType = GetCompressionType(requestContext, content, contentType);
                isHeadRequest = string.Equals(requestContext.Verb, "head", StringComparison.OrdinalIgnoreCase);
            }
            IHasHeaders result;
            if (string.IsNullOrEmpty(compressionType))
            {
                var contentLength = content.Length;
                if (isHeadRequest)
                {
                    content = Array.Empty();
                }
                result = new StreamWriter(content, contentType, contentLength, _logger);
            }
            else
            {
                result = GetCompressedResult(content, compressionType, responseHeaders, isHeadRequest, contentType);
            }
            if (responseHeaders == null)
            {
                responseHeaders = new Dictionary();
            }
            if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
            {
                responseHeaders["Expires"] = "-1";
            }
            AddResponseHeaders(result, responseHeaders);
            return result;
        }
        /// 
        /// Gets the HTTP result.
        /// 
        private IHasHeaders GetHttpResult(IRequest requestContext, string content, string contentType, bool addCachePrevention, IDictionary responseHeaders = null)
        {
            IHasHeaders result;
            var bytes = Encoding.UTF8.GetBytes(content);
            var compressionType = requestContext == null ? null : GetCompressionType(requestContext, bytes, contentType);
            var isHeadRequest = requestContext == null ? false : string.Equals(requestContext.Verb, "head", StringComparison.OrdinalIgnoreCase);
            if (string.IsNullOrEmpty(compressionType))
            {
                var contentLength = bytes.Length;
                if (isHeadRequest)
                {
                    bytes = Array.Empty();
                }
                result = new StreamWriter(bytes, contentType, contentLength, _logger);
            }
            else
            {
                result = GetCompressedResult(bytes, compressionType, responseHeaders, isHeadRequest, contentType);
            }
            if (responseHeaders == null)
            {
                responseHeaders = new Dictionary();
            }
            if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
            {
                responseHeaders["Expires"] = "-1";
            }
            AddResponseHeaders(result, responseHeaders);
            return result;
        }
        /// 
        /// Gets the optimized result.
        /// 
        /// 
        public object GetResult(IRequest requestContext, T result, IDictionary responseHeaders = null)
            where T : class
        {
            if (result == null)
            {
                throw new ArgumentNullException(nameof(result));
            }
            if (responseHeaders == null)
            {
                responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
            }
            responseHeaders["Expires"] = "-1";
            return ToOptimizedResultInternal(requestContext, result, responseHeaders);
        }
        private string GetCompressionType(IRequest request, byte[] content, string responseContentType)
        {
            if (responseContentType == null)
            {
                return null;
            }
            // Per apple docs, hls manifests must be compressed
            if (!responseContentType.StartsWith("text/", StringComparison.OrdinalIgnoreCase) &&
                responseContentType.IndexOf("json", StringComparison.OrdinalIgnoreCase) == -1 &&
                responseContentType.IndexOf("javascript", StringComparison.OrdinalIgnoreCase) == -1 &&
                responseContentType.IndexOf("xml", StringComparison.OrdinalIgnoreCase) == -1 &&
                responseContentType.IndexOf("application/x-mpegURL", StringComparison.OrdinalIgnoreCase) == -1)
            {
                return null;
            }
            if (content.Length < 1024)
            {
                return null;
            }
            return GetCompressionType(request);
        }
        private static string GetCompressionType(IRequest request)
        {
            var acceptEncoding = request.Headers["Accept-Encoding"];
            if (acceptEncoding != null)
            {
                //if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
                //    return "br";
                if (acceptEncoding.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1)
                    return "deflate";
                if (acceptEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1)
                    return "gzip";
            }
            return null;
        }
        /// 
        /// Returns the optimized result for the IRequestContext.
        /// Does not use or store results in any cache.
        /// 
        /// 
        /// 
        /// 
        public object ToOptimizedResult(IRequest request, T dto)
        {
            return ToOptimizedResultInternal(request, dto);
        }
        private object ToOptimizedResultInternal(IRequest request, T dto, IDictionary responseHeaders = null)
        {
            var contentType = request.ResponseContentType;
            switch (GetRealContentType(contentType))
            {
                case "application/xml":
                case "text/xml":
                case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml
                    return GetHttpResult(request, SerializeToXmlString(dto), contentType, false, responseHeaders);
                case "application/json":
                case "text/json":
                    return GetHttpResult(request, _jsonSerializer.SerializeToString(dto), contentType, false, responseHeaders);
                default:
                    break;
            }
            var isHeadRequest = string.Equals(request.Verb, "head", StringComparison.OrdinalIgnoreCase);
            var ms = new MemoryStream();
            var writerFn = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
            writerFn(dto, ms);
            ms.Position = 0;
            if (isHeadRequest)
            {
                using (ms)
                {
                    return GetHttpResult(request, Array.Empty(), contentType, true, responseHeaders);
                }
            }
            return GetHttpResult(request, ms, contentType, true, responseHeaders);
        }
        private IHasHeaders GetCompressedResult(byte[] content,
            string requestedCompressionType,
            IDictionary responseHeaders,
            bool isHeadRequest,
            string contentType)
        {
            if (responseHeaders == null)
            {
                responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase);
            }
            content = Compress(content, requestedCompressionType);
            responseHeaders["Content-Encoding"] = requestedCompressionType;
            responseHeaders["Vary"] = "Accept-Encoding";
            var contentLength = content.Length;
            if (isHeadRequest)
            {
                var result = new StreamWriter(Array.Empty(), contentType, contentLength, _logger);
                AddResponseHeaders(result, responseHeaders);
                return result;
            }
            else
            {
                var result = new StreamWriter(content, contentType, contentLength, _logger);
                AddResponseHeaders(result, responseHeaders);
                return result;
            }
        }
        private byte[] Compress(byte[] bytes, string compressionType)
        {
            if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase))
                return CompressBrotli(bytes);
            if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase))
                return Deflate(bytes);
            if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase))
                return GZip(bytes);
            throw new NotSupportedException(compressionType);
        }
        private byte[] CompressBrotli(byte[] bytes)
        {
            return _brotliCompressor.Compress(bytes);
        }
        private static byte[] Deflate(byte[] bytes)
        {
            // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream
            // Which means we must use MemoryStream since you have to use ToArray() on a closed Stream
            using (var ms = new MemoryStream())
            using (var zipStream = new DeflateStream(ms, CompressionMode.Compress))
            {
                zipStream.Write(bytes, 0, bytes.Length);
                zipStream.Dispose();
                return ms.ToArray();
            }
        }
        private static byte[] GZip(byte[] buffer)
        {
            using (var ms = new MemoryStream())
            using (var zipStream = new GZipStream(ms, CompressionMode.Compress))
            {
                zipStream.Write(buffer, 0, buffer.Length);
                zipStream.Dispose();
                return ms.ToArray();
            }
        }
        public static string GetRealContentType(string contentType)
        {
            return contentType == null
                       ? null
                       : contentType.Split(';')[0].ToLowerInvariant().Trim();
        }
        private static string SerializeToXmlString(object from)
        {
            using (var ms = new MemoryStream())
            {
                var xwSettings = new XmlWriterSettings();
                xwSettings.Encoding = new UTF8Encoding(false);
                xwSettings.OmitXmlDeclaration = false;
                using (var xw = XmlWriter.Create(ms, xwSettings))
                {
                    var serializer = new DataContractSerializer(from.GetType());
                    serializer.WriteObject(xw, from);
                    xw.Flush();
                    ms.Seek(0, SeekOrigin.Begin);
                    using (var reader = new StreamReader(ms))
                    {
                        return reader.ReadToEnd();
                    }
                }
            }
        }
        /// 
        /// Pres the process optimized result.
        /// 
        private object GetCachedResult(IRequest requestContext, IDictionary responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
        {
            bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
            if (!noCache)
            {
                if (IsNotModified(requestContext, cacheKey))
                {
                    AddAgeHeader(responseHeaders, lastDateModified);
                    AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
                    var result = new HttpResult(Array.Empty(), contentType ?? "text/html", HttpStatusCode.NotModified);
                    AddResponseHeaders(result, responseHeaders);
                    return result;
                }
            }
            AddCachingHeaders(responseHeaders, cacheKeyString, cacheDuration);
            return null;
        }
        public Task