// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.Text;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.Mvc.Infrastructure;

/// <summary>
/// A <see cref="IActionResultExecutor{ContentResult}"/> that is responsible for <see cref="ContentResult"/>
/// </summary>
public partial class ContentResultExecutor : IActionResultExecutor<ContentResult>
{
    private const string DefaultContentType = "text/plain; charset=utf-8";
    private readonly ILogger<ContentResultExecutor> _logger;
    private readonly IHttpResponseStreamWriterFactory _httpResponseStreamWriterFactory;

    /// <summary>
    /// Initializes a new instance of <see cref="ContentResultExecutor"/>.
    /// </summary>
    /// <param name="logger">The logger to use.</param>
    /// <param name="httpResponseStreamWriterFactory">The stream writer factory.</param>
    public ContentResultExecutor(ILogger<ContentResultExecutor> logger, IHttpResponseStreamWriterFactory httpResponseStreamWriterFactory)
    {
        _logger = logger;
        _httpResponseStreamWriterFactory = httpResponseStreamWriterFactory;
    }

    /// <inheritdoc />
    public virtual async Task ExecuteAsync(ActionContext context, ContentResult result)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(result);

        var response = context.HttpContext.Response;

        ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
            result.ContentType,
            response.ContentType,
            (DefaultContentType, Encoding.UTF8),
            MediaType.GetEncoding,
            out var resolvedContentType,
            out var resolvedContentTypeEncoding);

        response.ContentType = resolvedContentType;

        if (result.StatusCode != null)
        {
            response.StatusCode = result.StatusCode.Value;
        }

        Log.ContentResultExecuting(_logger, resolvedContentType);

        if (result.Content != null)
        {
            response.ContentLength = resolvedContentTypeEncoding.GetByteCount(result.Content);

            await using (var textWriter = _httpResponseStreamWriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding))
            {
                await textWriter.WriteAsync(result.Content);

                // Flushing the HttpResponseStreamWriter does not flush the underlying stream. This just flushes
                // the buffered text in the writer.
                // We do this rather than letting dispose handle it because dispose would call Write and we want
                // to call WriteAsync.
                await textWriter.FlushAsync();
            }
        }
    }

    private static partial class Log
    {
        [LoggerMessage(1, LogLevel.Information, "Executing ContentResult with HTTP Response ContentType of {ContentType}", EventName = "ContentResultExecuting")]
        public static partial void ContentResultExecuting(ILogger logger, string contentType);
    }
}
