using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using OI.Metrology.Archive.Models;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OI.Metrology.Archive;

// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class ApiLoggingMiddleware
{
    private readonly RequestDelegate _Next;
    private readonly AppSettings _AppSettings;

    public ApiLoggingMiddleware(RequestDelegate next, AppSettings appSettings)
    {
        _Next = next;
        _AppSettings = appSettings;
    }

    // this is the method called in ASP.NET Core middleware to handle an HTTP request
    // the middleware allows you to add code to run before and after an http request is handled
    // they are stacked together like a pipeline
    public async Task Invoke(HttpContext httpContext)
    {
        try
        {
            bool doLogging = false;
            // check to see if this is a request path that is enabled for logging
            if (!string.IsNullOrWhiteSpace(_AppSettings.ApiLoggingPathPrefixes))
            {
                // check if the request path begins with any part of pathsToLog
                if (_AppSettings.ApiLoggingPathPrefixes.Split(';').Any(p => httpContext.Request.Path.StartsWithSegments(new PathString(p))))
                {
                    if (string.IsNullOrWhiteSpace(_AppSettings.ApiLoggingContentTypes))
                    {
                        // if no content type filter is defined, log all content types
                        doLogging = true;
                    }
                    else
                    {
                        // if there are content type filters configured, only log is the request begins with one of them
                        doLogging = _AppSettings.ApiLoggingContentTypes.Split(';').Any(ct => httpContext.Request.ContentType.StartsWith(ct));
                    }
                }
            }

            // if logging is enabled for this request
            if (doLogging)
            {
                DateTime startTime = DateTime.Now;

                // get request body
                string requestBodyContent = await ReadRequestBody(httpContext.Request);

                // save the original response and stream
                Stream originalBodyStream = httpContext.Response.Body;

                using MemoryStream responseBody = new();
                // replace response stream with our memory stream
                httpContext.Response.Body = responseBody;

                // call next middleware, this is to process the request so we have a response to inspect
                await _Next(httpContext);

                // get response body into string 
                _ = httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
                string bodyAsText = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();

                // copy memory stream to original response stream
                _ = httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
                await httpContext.Response.Body.CopyToAsync(originalBodyStream);

                // log the request and response
                LogRequestAndResponse(startTime, httpContext.Request, requestBodyContent, bodyAsText);
                return;
            }
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine("Error in ApiLoggingMiddleware: " + ex.ToString());
        }

        // proceed with the http request normally
        await _Next(httpContext);
    }

    private static async Task<string> ReadRequestBody(HttpRequest request)
    {
        HttpRequestRewindExtensions.EnableBuffering(request);

        byte[] buffer = new byte[Convert.ToInt32(request.ContentLength)];
        _ = await request.Body.ReadAsync(buffer);
        string bodyAsText = Encoding.UTF8.GetString(buffer);
        _ = request.Body.Seek(0, SeekOrigin.Begin);

        return bodyAsText;
    }

    private void LogRequestAndResponse(DateTime requestTime, HttpRequest request, string requestBody, string responseBody)
    {
        int threadId = Environment.CurrentManagedThreadId;

        string fileName = $"ApiLog{requestTime:yyyyMMdd_hhmmssttt}_{threadId}.txt";

        HttpContext context = request.HttpContext;

        if (!Directory.Exists(_AppSettings.ApiLogPath))
            _ = Directory.CreateDirectory(_AppSettings.ApiLogPath);

        using StreamWriter sw = new(Path.Join(_AppSettings.ApiLogPath, fileName), true);
        sw.WriteLine($"Request at {requestTime:yyyy/MM/dd hh:mm:ss.ttt} from {context.Connection.RemoteIpAddress}");
        sw.WriteLine($"{request.Method} {request.Path} {request.QueryString}");
        sw.WriteLine("Request body:");
        sw.WriteLine(requestBody);
        sw.WriteLine($"Response at {DateTime.Now:yyyy/MM/dd hh:mm:ss.ttt}");
        sw.WriteLine(responseBody);
        sw.WriteLine("==========");
    }
}

// Extension method used to add the middleware to the HTTP request pipeline.
public static class ApiLoggingMiddlewareExtensions
{
    public static IApplicationBuilder UseApiLoggingMiddleware(this IApplicationBuilder builder) => builder.UseMiddleware<ApiLoggingMiddleware>();
}