using System.Diagnostics;
using System.Text;
using Microsoft.Extensions.Configuration;
using Serilog;

namespace MyNewProjectName.AdminAPI.Middleware;

/// <summary>
/// Middleware to log request and response bodies
/// WARNING: This can generate large log files. Use with caution in production.
/// Consider enabling only for specific environments or endpoints.
/// </summary>
public class RequestResponseLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly Serilog.ILogger _logger;
    private readonly bool _enableRequestLogging;
    private readonly bool _enableResponseLogging;

    // Paths that should NOT be logged (e.g., health checks, metrics)
    private static readonly string[] ExcludedPaths = new[]
    {
        "/health",
        "/metrics",
        "/favicon.ico"
    };

    public RequestResponseLoggingMiddleware(
        RequestDelegate next,
        Serilog.ILogger logger,
        IConfiguration configuration)
    {
        _next = next;
        _logger = logger;
        _enableRequestLogging = configuration.GetValue<bool>("Logging:EnableRequestLogging", false);
        _enableResponseLogging = configuration.GetValue<bool>("Logging:EnableResponseLogging", false);
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Skip logging for excluded paths
        if (ExcludedPaths.Any(path => context.Request.Path.StartsWithSegments(path)))
        {
            await _next(context);
            return;
        }

        var stopwatch = Stopwatch.StartNew();
        var requestBody = string.Empty;
        var responseBody = string.Empty;

        // Log request
        if (_enableRequestLogging)
        {
            requestBody = await ReadRequestBodyAsync(context.Request);
            _logger.Information(
                "Request: {Method} {Path} {QueryString} | Body: {RequestBody}",
                context.Request.Method,
                context.Request.Path,
                context.Request.QueryString,
                requestBody
            );
        }

        // Capture response body
        var originalBodyStream = context.Response.Body;
        using var responseBodyStream = new MemoryStream();
        context.Response.Body = responseBodyStream;

        try
        {
            await _next(context);
        }
        finally
        {
            stopwatch.Stop();

            // Log response
            if (_enableResponseLogging)
            {
                responseBody = await ReadResponseBodyAsync(context.Response);
                await responseBodyStream.CopyToAsync(originalBodyStream);

                _logger.Information(
                    "Response: {StatusCode} | Duration: {Duration}ms | Body: {ResponseBody}",
                    context.Response.StatusCode,
                    stopwatch.ElapsedMilliseconds,
                    responseBody
                );
            }
            else
            {
                await responseBodyStream.CopyToAsync(originalBodyStream);
            }

            context.Response.Body = originalBodyStream;
        }
    }

    private static async Task<string> ReadRequestBodyAsync(HttpRequest request)
    {
        // Enable buffering to allow reading the body multiple times
        request.EnableBuffering();

        using var reader = new StreamReader(
            request.Body,
            encoding: Encoding.UTF8,
            detectEncodingFromByteOrderMarks: false,
            leaveOpen: true);

        var body = await reader.ReadToEndAsync();
        request.Body.Position = 0; // Reset position for next middleware

        // Truncate very long bodies to avoid huge logs
        return body.Length > 10000 ? body[..10000] + "... (truncated)" : body;
    }

    private static async Task<string> ReadResponseBodyAsync(HttpResponse response)
    {
        response.Body.Seek(0, SeekOrigin.Begin);
        using var reader = new StreamReader(
            response.Body,
            encoding: Encoding.UTF8,
            detectEncodingFromByteOrderMarks: false,
            leaveOpen: true);

        var body = await reader.ReadToEndAsync();
        response.Body.Seek(0, SeekOrigin.Begin); // Reset position

        // Truncate very long bodies to avoid huge logs
        return body.Length > 10000 ? body[..10000] + "... (truncated)" : body;
    }
}
