using Hotline.Logger.Models;
using Microsoft.Extensions.Configuration;
using Hotline.Share.Tools;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Serilog;
using System.Text;

namespace Hotline.Logger
{

    /// <summary>
    /// Acc 日志记录
    /// </summary>
    public class RequestResponseLoggingMiddleware
    {
        /// <summary>
        /// 组件
        /// </summary>
        private readonly RequestDelegate _next;

        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly Serilog.ILogger _logger;

        public RequestResponseLoggingMiddleware(RequestDelegate next)
        {
            _next = next;
            _logger = new LoggerConfiguration()
                .WriteTo.Logger(configure => configure 
                .MinimumLevel.Debug()
                .WriteTo.RollingFile(
                    Path.Combine("logs", "acc-log-{Date}.txt"),
                    retainedFileCountLimit: 7,
                    outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
                ))
                .CreateLogger();
        }

        /// <summary>
        /// 日志记录器
        /// </summary>
        /// <param name="context"> The context.  </param>
        /// <returns> The <see cref="Task"/>.  </returns>
        public async Task Invoke(HttpContext context)
        {
            var injectedRequestStream = new MemoryStream();
            try
            {
                var requestTime = DateTime.Now;
                var accModel = new AccModel();
                context.Items.TryAdd("AccId", accModel.Id);
                await GetMethod(context, injectedRequestStream, accModel);
                var originalBodyStream = context.Response.Body;
                using (var responseBody = new MemoryStream())
                {
                    context.Response.Body = responseBody;
                    await _next(context);

                    accModel = await FormatResponse(context.Response, requestTime, accModel);
                    if (context.Items.TryGetValue("ErrorId", out var errorId))
                        accModel.ErrorId = errorId.ToString();
                    _logger.Information(accModel.ToJson());
                    await responseBody.CopyToAsync(originalBodyStream);
                }

            }
            catch (Exception e)
            {
                throw new Exception("Hotline.Logger.RequestResponseLoggingMiddleware.GetMethod", e);
            }
            finally
            {
                injectedRequestStream.Dispose();
            }
        }

        /// <summary>
        /// 获取 响应日志数据
        /// </summary>
        /// <param name="response"> The response.  </param>
        /// <param name="requestTime"> The request time.  </param>
        /// <returns> The <see cref="Task"/>.  </returns>
        private async Task<AccModel> FormatResponse(HttpResponse response, DateTime requestTime, AccModel accModel)
        {
            response.Body.Seek(0, SeekOrigin.Begin);
            var text = await new StreamReader(response.Body).ReadToEndAsync();
            response.Body.Seek(0, SeekOrigin.Begin);
            if (response.ContentType.Contains("application/json"))
            { 
                accModel.OutResult = text;
            }
            accModel.Interval = (long)(DateTime.Now - requestTime).TotalMilliseconds;
            return accModel;
        }

        /// <summary>
        /// 获取请求入参和方法
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="injectedRequestStream">
        /// The injected Request Stream.
        /// </param>
        private async Task GetMethod(HttpContext context, MemoryStream injectedRequestStream, AccModel accModel)
        {
            try
            {
                if (context.Request.Method.ToUpper() == "GET")
                {
                    try
                    {
                        accModel.InParam = context.Request.QueryString.ToString();
                        accModel.Method = context.Request.Path;
                        accModel.ServiceUrl = context.Request.Host.ToString();
                    }
                    catch (Exception e)
                    {
                        accModel.InParam = "GetMethod.Get 代码块异常;";
                        accModel.Method = "GetMethod.Get 代码块异常;";
                        accModel.ServiceUrl = e.ToString();
                    }
                }

                if (context.Request.Method.ToUpper() == "POST" || context.Request.Method.ToUpper() == "PUT" || context.Request.Method.ToUpper() == "DELETE")
                {
                    if (context.Request.ContentType?.IndexOf("multipart/form-data", StringComparison.Ordinal) == -1)
                    {
                        using var bodyReader = new StreamReader(context.Request.Body);
                        var bodyAsText = await bodyReader.ReadToEndAsync();

                        var bytesToWrite = Encoding.UTF8.GetBytes(bodyAsText);
                        injectedRequestStream.Write(bytesToWrite, 0, bytesToWrite.Length);
                        injectedRequestStream.Seek(0, SeekOrigin.Begin);
                        context.Request.Body = injectedRequestStream;

                        accModel.InParam = bodyAsText;
                    }
                    accModel.Method = context.Request.Path;
                    accModel.ServiceUrl = context.Request.Host.ToString();
                }

                try
                {
                    accModel.User = context.User.Identities
                        .FirstOrDefault()?.Claims
                        .Where(m => m.Type == accModel.UserKey)
                        .Select(m => m.Value)
                        .FirstOrDefault() ?? string.Empty;
                }
                catch { }
            }
            catch (Exception e)
            {
                _logger.Error($"Hotline.Logger.RequestResponseLoggingMiddleware.GetMethod: {e}");
            }

        }
    }

    /// <summary>
    /// 注册 acc 日志记录器
    /// </summary>
    public static class RequestResponseLoggingMiddlewareExtensions
    {
        /// <summary>
        /// 注册日志记录服务
        /// </summary>
        /// <param name="builder">
        /// The builder.
        /// </param>
        /// <returns>
        /// The <see cref="IApplicationBuilder"/>.
        /// </returns>
        public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder, IConfiguration configuration)
        {
            var isAccLog = configuration.GetSection("AccLog").Get<bool>();
            if (isAccLog)
            {
                return builder.UseMiddleware<RequestResponseLoggingMiddleware>();
            }
            return builder;
        }
    }
}
