using System.Diagnostics;
using System.Text;

namespace Flyshot.Server.Host.Middleware;

/// <summary>
/// HTTP 请求与响应日志中间件。
/// 记录每个 HTTP 请求的进入时间、方法、路径、查询串、请求体，
/// 以及响应的状态码、耗时和响应体（调试级别）。
/// </summary>
public sealed class RequestResponseLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestResponseLoggingMiddleware> _logger;

    /// <summary>
    /// 请求体最大日志长度，超出则截断并附加省略标记。
    /// </summary>
    private const int MaxBodyLogLength = 4096;

    /// <summary>
    /// 请求/响应日志忽略路径前缀列表，用于跳过高频轮询接口的常规日志。
    /// </summary>
    private static readonly string[] IgnoredLogPathPrefixes =
    [
        "/api/status/snapshot"
    ];

    /// <summary>
    /// 初始化请求响应日志中间件。
    /// </summary>
    /// <param name="next">下一个中间件委托。</param>
    /// <param name="logger">日志记录器。</param>
    public RequestResponseLoggingMiddleware(RequestDelegate next, ILogger<RequestResponseLoggingMiddleware> logger)
    {
        _next = next ?? throw new ArgumentNullException(nameof(next));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    /// <summary>
    /// 处理 HTTP 请求并记录输入输出。
    /// </summary>
    /// <param name="context">HTTP 上下文。</param>
    public async Task InvokeAsync(HttpContext context)
    {
        // 高频状态轮询接口只转发请求，不记录请求/响应日志，避免控制台和文件日志被刷屏。
        if (ShouldSkipRequestResponseLog(context.Request.Path))
        {
            await _next(context).ConfigureAwait(false);
            return;
        }

        var stopwatch = Stopwatch.StartNew();
        var request = context.Request;
        var requestId = Activity.Current?.Id ?? context.TraceIdentifier;

        // 记录请求进入信息（Info 级别：方法、路径、查询参数）。
        _logger.LogInformation(
            "[HTTP-REQ] [{RequestId}] {Method} {Path}{QueryString} — 客户端 {RemoteIp}",
            requestId,
            request.Method,
            request.Path,
            request.QueryString.HasValue ? request.QueryString.Value : string.Empty,
            context.Connection.RemoteIpAddress);

        // 读取并记录请求体（Debug 级别）。
        string? requestBody = null;
        if (request.ContentLength > 0 && request.Body.CanRead)
        {
            request.EnableBuffering();
            requestBody = await ReadBodyAsync(request.Body, context.RequestAborted).ConfigureAwait(false);
            request.Body.Position = 0;

            if (!string.IsNullOrEmpty(requestBody))
            {
                _logger.LogDebug(
                    "[HTTP-REQ-BODY] [{RequestId}] {Body}",
                    requestId,
                    TruncateBody(requestBody));
            }
        }

        // 拦截响应流以便读取响应体。
        var originalResponseBody = context.Response.Body;
        using var responseBodyStream = new MemoryStream();
        context.Response.Body = responseBodyStream;

        try
        {
            await _next(context).ConfigureAwait(false);
        }
        catch (Exception exception)
        {
            _logger.LogError(
                exception,
                "[HTTP-ERR] [{RequestId}] {Method} {Path} 处理过程中发生未捕获异常",
                requestId,
                request.Method,
                request.Path);
            throw;
        }
        finally
        {
            stopwatch.Stop();
            responseBodyStream.Position = 0;
            var responseBody = await ReadBodyAsync(responseBodyStream, context.RequestAborted).ConfigureAwait(false);
            responseBodyStream.Position = 0;
            await responseBodyStream.CopyToAsync(originalResponseBody, context.RequestAborted).ConfigureAwait(false);
            context.Response.Body = originalResponseBody;

            var statusCode = context.Response.StatusCode;
            var level = statusCode >= 500 ? LogLevel.Error : statusCode >= 400 ? LogLevel.Warning : LogLevel.Information;

            // 记录响应概要（Info/Warning/Error 级别）。
            _logger.Log(
                level,
                "[HTTP-RES] [{RequestId}] {Method} {Path} => {StatusCode} ({ElapsedMs}ms)",
                requestId,
                request.Method,
                request.Path,
                statusCode,
                stopwatch.ElapsedMilliseconds);

            // 记录响应体（Debug 级别）。
            if (!string.IsNullOrEmpty(responseBody))
            {
                _logger.LogDebug(
                    "[HTTP-RES-BODY] [{RequestId}] {Body}",
                    requestId,
                    TruncateBody(responseBody));
            }
        }
    }

    /// <summary>
    /// 从流中读取文本内容。
    /// </summary>
    private static async Task<string> ReadBodyAsync(Stream stream, CancellationToken cancellationToken)
    {
        if (!stream.CanRead)
        {
            return string.Empty;
        }

        using var reader = new StreamReader(stream, Encoding.UTF8, leaveOpen: true);
        var body = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
        return body;
    }

    /// <summary>
    /// 截断过长内容，避免日志膨胀。
    /// </summary>
    private static string TruncateBody(string body)
    {
        if (body.Length <= MaxBodyLogLength)
        {
            return body;
        }

        return body[..MaxBodyLogLength] + " ... [截断，总长度=" + body.Length + "]";
    }

    /// <summary>
    /// 判断当前请求路径是否命中请求/响应日志忽略前缀。
    /// </summary>
    private static bool ShouldSkipRequestResponseLog(PathString path)
    {
        var pathValue = path.Value;
        return !string.IsNullOrEmpty(pathValue)
            && IgnoredLogPathPrefixes.Any(prefix => pathValue.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
    }
}
