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

using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;

namespace ApiExplorerWebSite;

/// <summary>
/// A resource filter that looks up and serializes Api Explorer data for the action.
///
/// This replaces the 'actual' output of the action.
/// </summary>
public class ApiExplorerDataFilter : IResourceFilter
{
    private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider;

    public ApiExplorerDataFilter(IApiDescriptionGroupCollectionProvider descriptionProvider)
    {
        _descriptionProvider = descriptionProvider;
    }

    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor &&
            controllerActionDescriptor.MethodInfo.IsDefined(typeof(PassThruAttribute)))
        {
            return;
        }

        var descriptions = new List<ApiExplorerData>();
        foreach (var group in _descriptionProvider.ApiDescriptionGroups.Items)
        {
            foreach (var description in group.Items)
            {
                if (context.ActionDescriptor == description.ActionDescriptor)
                {
                    descriptions.Add(CreateSerializableData(description));
                }
            }
        }

        context.Result = new JsonResult(descriptions);
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }

    private ApiExplorerData CreateSerializableData(ApiDescription description)
    {
        var data = new ApiExplorerData()
        {
            GroupName = description.GroupName,
            HttpMethod = description.HttpMethod,
            RelativePath = description.RelativePath
        };

        foreach (var parameter in description.ParameterDescriptions)
        {
            var parameterData = new ApiExplorerParameterData()
            {
                Name = parameter.Name,
                Source = parameter.Source.Id,
                Type = parameter.Type?.FullName,
                DefaultValue = parameter.DefaultValue?.ToString(),
                IsRequired = parameter.IsRequired,
            };

            if (parameter.RouteInfo != null)
            {
                parameterData.RouteInfo = new ApiExplorerParameterRouteInfo()
                {
                    ConstraintTypes = parameter.RouteInfo.Constraints?.Select(c => c.GetType().Name).ToArray(),
                    DefaultValue = parameter.RouteInfo.DefaultValue,
                    IsOptional = parameter.RouteInfo.IsOptional,
                };
            }

            data.ParameterDescriptions.Add(parameterData);
        }

        foreach (var request in description.SupportedRequestFormats)
        {
            data.SupportedRequestFormats.Add(new ApiExplorerRequestFormat
            {
                FormatterType = request.Formatter?.GetType().FullName,
                MediaType = request.MediaType,
            });
        }

        foreach (var response in description.SupportedResponseTypes)
        {
            var responseType = new ApiExplorerResponseType()
            {
                StatusCode = response.StatusCode,
                ResponseType = response.Type?.FullName,
                IsDefaultResponse = response.IsDefaultResponse,
            };

            foreach (var responseFormat in response.ApiResponseFormats)
            {
                responseType.ResponseFormats.Add(new ApiExplorerResponseFormat()
                {
                    FormatterType = responseFormat.Formatter?.GetType().FullName,
                    MediaType = responseFormat.MediaType
                });
            }

            data.SupportedResponseTypes.Add(responseType);
        }

        return data;
    }

    // Used to serialize data between client and server
    private class ApiExplorerData
    {
        public string GroupName { get; set; }

        public string HttpMethod { get; set; }

        public List<ApiExplorerParameterData> ParameterDescriptions { get; } = new List<ApiExplorerParameterData>();

        public string RelativePath { get; set; }

        public List<ApiExplorerResponseType> SupportedResponseTypes { get; } = new List<ApiExplorerResponseType>();

        public List<ApiExplorerRequestFormat> SupportedRequestFormats { get; } = new List<ApiExplorerRequestFormat>();
    }

    // Used to serialize data between client and server
    private class ApiExplorerParameterData
    {
        public string Name { get; set; }

        public ApiExplorerParameterRouteInfo RouteInfo { get; set; }

        public string Source { get; set; }

        public string Type { get; set; }

        public string DefaultValue { get; set; }

        public bool IsRequired { get; set; }
    }

    // Used to serialize data between client and server
    private class ApiExplorerParameterRouteInfo
    {
        public string[] ConstraintTypes { get; set; }

        public object DefaultValue { get; set; }

        public bool IsOptional { get; set; }
    }

    // Used to serialize data between client and server
    private class ApiExplorerResponseType
    {
        public IList<ApiExplorerResponseFormat> ResponseFormats { get; }
            = new List<ApiExplorerResponseFormat>();

        public string ResponseType { get; set; }

        public int StatusCode { get; set; }

        public bool IsDefaultResponse { get; set; }
    }

    private class ApiExplorerResponseFormat
    {
        public string MediaType { get; set; }

        public string FormatterType { get; set; }
    }

    private class ApiExplorerRequestFormat
    {
        public string MediaType { get; set; }

        public string FormatterType { get; set; }
    }
}
