using System.Reflection;
using Hua.Todo.Application.Interfaces;
using Hua.Todo.Application.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.OpenApi;

namespace Hua.Todo.Application.DynamicApi.Swagger;

/// <summary>
/// 为 Dynamic API（<c>/api/{service}/...</c>）补齐 OpenAPI 文档的过滤器。
/// 说明：Dynamic API 通过中间件反射分发，不会被 ASP.NET Core 的 ApiExplorer 自动发现；
/// 因此需要在 Swagger 生成阶段手动把这些端点补充到 OpenAPI Paths 中。
/// </summary>
public sealed class DynamicApiSwaggerDocumentFilter : IDocumentFilter
{
    /// <summary>
    /// 将 Dynamic API 端点追加到 <paramref name="swaggerDoc"/>。
    /// </summary>
    /// <param name="swaggerDoc">待输出的 OpenAPI 文档。</param>
    /// <param name="context">Swagger 生成上下文（用于 Schema 生成与引用管理）。</param>
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        var dynamicApiAssembly = typeof(IDynamicApiService).Assembly;
        var serviceInterfaces = dynamicApiAssembly
            .GetTypes()
            .Where(t => t.IsInterface && typeof(IDynamicApiService).IsAssignableFrom(t))
            .Where(IsRemoteServiceEnabled)
            .ToList();

        foreach (var serviceInterface in serviceInterfaces)
        {
            var serviceSegment = GetServiceRouteSegment(serviceInterface);
            if (string.IsNullOrWhiteSpace(serviceSegment))
            {
                continue;
            }

            AddServiceOperations(swaggerDoc, context, serviceInterface, serviceSegment);
        }
    }

    private static void AddServiceOperations(
        OpenApiDocument swaggerDoc,
        DocumentFilterContext context,
        Type serviceInterface,
        string serviceSegment)
    {
        var methods = serviceInterface
            .GetMethods(BindingFlags.Public | BindingFlags.Instance)
            .Where(IsRemoteServiceEnabled)
            .ToList();

        foreach (var method in methods)
        {
            var httpMethod = GetHttpMethod(method);
            if (string.IsNullOrWhiteSpace(httpMethod))
            {
                continue;
            }

            if (!TryGetRouteSuffix(serviceInterface, method, out var routeSuffix))
            {
                continue;
            }

            var fullPath = BuildFullPath(serviceSegment, routeSuffix);
            if (!swaggerDoc.Paths.TryGetValue(fullPath, out var pathItem))
            {
                pathItem = new OpenApiPathItem();
                swaggerDoc.Paths[fullPath] = pathItem;
            }

            var operation = BuildOperation(context, serviceSegment, method, httpMethod, routeSuffix);
            TrySetOperation(pathItem, httpMethod, operation);
        }
    }

    private static OpenApiOperation BuildOperation(
        DocumentFilterContext context,
        string serviceSegment,
        MethodInfo method,
        string httpMethod,
        string routeSuffix)
    {
        var operation = new OpenApiOperation
        {
            OperationId = $"DynamicApi_{serviceSegment}_{httpMethod}_{method.Name}",
            Summary = $"{serviceSegment} - {method.Name}",
            Tags = new HashSet<OpenApiTagReference> { new OpenApiTagReference(serviceSegment, null, serviceSegment) },
            Responses = BuildResponses(context, method)
        };

        AddParametersAndBody(context, operation, method, routeSuffix, httpMethod);

        return operation;
    }

    private static OpenApiResponses BuildResponses(DocumentFilterContext context, MethodInfo method)
    {
        var returnDataType = UnwrapReturnDataType(method.ReturnType) ?? typeof(object);
        var responseType = typeof(ApiResponse<>).MakeGenericType(returnDataType);
        var responseSchema = context.SchemaGenerator.GenerateSchema(responseType, context.SchemaRepository);

        return new OpenApiResponses
        {
            ["200"] = new OpenApiResponse
            {
                Description = "OK",
                Content = new Dictionary<string, OpenApiMediaType>
                {
                    ["application/json"] = new OpenApiMediaType { Schema = responseSchema }
                }
            }
        };
    }

    private static void AddParametersAndBody(
        DocumentFilterContext context,
        OpenApiOperation operation,
        MethodInfo method,
        string routeSuffix,
        string httpMethod)
    {
        var routeParams = ExtractRouteParameters(routeSuffix);
        if (routeParams.Count > 0)
        {
            operation.Parameters ??= new List<IOpenApiParameter>();

            foreach (var routeParamName in routeParams)
            {
                var paramInfo = method.GetParameters()
                    .FirstOrDefault(p => string.Equals(p.Name, routeParamName, StringComparison.OrdinalIgnoreCase));

                var paramType = paramInfo?.ParameterType ?? typeof(string);
                var schema = context.SchemaGenerator.GenerateSchema(paramType, context.SchemaRepository);

                operation.Parameters.Add(new OpenApiParameter
                {
                    Name = routeParamName,
                    In = ParameterLocation.Path,
                    Required = true,
                    Schema = schema
                });
            }
        }

        var requestBodyParameter = GetRequestBodyParameter(method, httpMethod);
        if (requestBodyParameter == null)
        {
            return;
        }

        var bodySchema = context.SchemaGenerator.GenerateSchema(requestBodyParameter.ParameterType, context.SchemaRepository);
        operation.RequestBody = new OpenApiRequestBody
        {
            Required = true,
            Content = new Dictionary<string, OpenApiMediaType>
            {
                ["application/json"] = new OpenApiMediaType { Schema = bodySchema }
            }
        };
    }

    private static ParameterInfo? GetRequestBodyParameter(MethodInfo method, string httpMethod)
    {
        if (string.Equals(httpMethod, "GET", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(httpMethod, "DELETE", StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        var parameters = method.GetParameters();
        if (parameters.Length == 0)
        {
            return null;
        }

        var bodyParam = parameters.FirstOrDefault(p => p.GetCustomAttribute<FromBodyAttribute>() != null);
        if (bodyParam != null)
        {
            return bodyParam;
        }

        return parameters.FirstOrDefault(p => !IsSimpleType(p.ParameterType));
    }

    private static Type? UnwrapReturnDataType(Type returnType)
    {
        if (returnType == typeof(void))
        {
            return null;
        }

        if (returnType == typeof(Task))
        {
            return null;
        }

        if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
        {
            return returnType.GetGenericArguments()[0];
        }

        return returnType;
    }

    private static string BuildFullPath(string serviceSegment, string routeSuffix)
    {
        var normalizedSuffix = string.IsNullOrWhiteSpace(routeSuffix)
            ? string.Empty
            : routeSuffix.StartsWith("/", StringComparison.Ordinal) ? routeSuffix : $"/{routeSuffix}";

        return $"/api/{serviceSegment}{normalizedSuffix}";
    }

    private static List<string> ExtractRouteParameters(string routeSuffix)
    {
        var list = new List<string>();
        var suffix = routeSuffix ?? string.Empty;

        var startIndex = 0;
        while (startIndex < suffix.Length)
        {
            var open = suffix.IndexOf('{', startIndex);
            if (open < 0)
            {
                break;
            }

            var close = suffix.IndexOf('}', open + 1);
            if (close < 0)
            {
                break;
            }

            var name = suffix.Substring(open + 1, close - open - 1).Trim();
            if (!string.IsNullOrWhiteSpace(name))
            {
                list.Add(name);
            }

            startIndex = close + 1;
        }

        return list;
    }

    private static bool TryGetRouteSuffix(Type serviceInterface, MethodInfo method, out string routeSuffix)
    {
        routeSuffix = string.Empty;

        var explicitRoute = method.GetCustomAttribute<HttpGetAttribute>()?.Route
                            ?? method.GetCustomAttribute<HttpPostAttribute>()?.Route
                            ?? method.GetCustomAttribute<HttpPutAttribute>()?.Route
                            ?? method.GetCustomAttribute<HttpDeleteAttribute>()?.Route
                            ?? method.GetCustomAttribute<HttpPatchAttribute>()?.Route;

        if (!string.IsNullOrWhiteSpace(explicitRoute))
        {
            routeSuffix = explicitRoute.Trim();
            return true;
        }

        if (string.Equals(serviceInterface.Name, "ITaskService", StringComparison.OrdinalIgnoreCase))
        {
            return TryGetTaskServiceRouteSuffix(method, out routeSuffix);
        }

        return false;
    }

    private static bool TryGetTaskServiceRouteSuffix(MethodInfo method, out string routeSuffix)
    {
        routeSuffix = string.Empty;

        return method.Name switch
        {
            "GetAllTasksAsync" => Return(string.Empty, out routeSuffix),
            "GetTaskByIdAsync" => Return("/{id}", out routeSuffix),
            "GetActiveTasksAsync" => Return("/active", out routeSuffix),
            "GetCompletedTasksAsync" => Return("/completed", out routeSuffix),
            "CreateTaskAsync" => Return(string.Empty, out routeSuffix),
            "UpdateTaskAsync" => Return(string.Empty, out routeSuffix),
            "ToggleCompleteAsync" => Return("/{id}/toggle", out routeSuffix),
            "DeleteTaskAsync" => Return("/{id}", out routeSuffix),
            "GetSubTasksAsync" => Return("/{parentTaskId}/subtasks", out routeSuffix),
            _ => false
        };
    }

    private static bool Return(string value, out string routeSuffix)
    {
        routeSuffix = value;
        return true;
    }

    private static void TrySetOperation(IOpenApiPathItem pathItem, string httpMethod, OpenApiOperation operation)
    {
        var operationsObject = pathItem.GetType().GetProperty("Operations")?.GetValue(pathItem);
        if (operationsObject == null)
        {
            return;
        }

        if (operationsObject is System.Collections.IDictionary dict)
        {
            var dictType = operationsObject.GetType();
            var keyType = dictType.IsGenericType ? dictType.GetGenericArguments()[0] : typeof(object);
            dict[CreateOperationKey(keyType, httpMethod)] = operation;
            return;
        }

        var indexer = operationsObject.GetType().GetProperty("Item");
        var keyTypeFromIndexer = indexer?.GetIndexParameters().FirstOrDefault()?.ParameterType ?? typeof(object);
        indexer?.SetValue(operationsObject, operation, new[] { CreateOperationKey(keyTypeFromIndexer, httpMethod) });
    }

    private static object CreateOperationKey(Type keyType, string httpMethod)
    {
        if (keyType == typeof(string))
        {
            return httpMethod.ToLowerInvariant();
        }

        if (keyType.IsEnum)
        {
            var name = httpMethod.ToUpperInvariant() switch
            {
                "GET" => "Get",
                "POST" => "Post",
                "PUT" => "Put",
                "DELETE" => "Delete",
                "PATCH" => "Patch",
                _ => httpMethod
            };

            return Enum.Parse(keyType, name, ignoreCase: true);
        }

        return httpMethod;
    }

    private static bool IsRemoteServiceEnabled(MemberInfo memberInfo)
    {
        var attribute = memberInfo.GetCustomAttribute<RemoteServiceAttribute>();
        return attribute == null || attribute.IsEnabled;
    }

    private static string GetServiceRouteSegment(Type serviceInterface)
    {
        var name = serviceInterface.Name;
        if (name.EndsWith("Service", StringComparison.OrdinalIgnoreCase))
        {
            name = name.Substring(0, name.Length - "Service".Length);
        }

        if (name.StartsWith("I", StringComparison.Ordinal) &&
            name.Length > 1 &&
            char.IsUpper(name[1]))
        {
            name = name.Substring(1);
        }

        if (name.EndsWith("App", StringComparison.OrdinalIgnoreCase))
        {
            name = name.Substring(0, name.Length - "App".Length);
        }

        return name.ToLowerInvariant();
    }

    private static string GetHttpMethod(MethodInfo method)
    {
        if (method.GetCustomAttribute<HttpGetAttribute>() != null) return "GET";
        if (method.GetCustomAttribute<HttpPostAttribute>() != null) return "POST";
        if (method.GetCustomAttribute<HttpPutAttribute>() != null) return "PUT";
        if (method.GetCustomAttribute<HttpDeleteAttribute>() != null) return "DELETE";
        if (method.GetCustomAttribute<HttpPatchAttribute>() != null) return "PATCH";

        if (method.Name.StartsWith("Toggle", StringComparison.OrdinalIgnoreCase)) return "PATCH";
        if (method.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase)) return "GET";
        if (method.Name.StartsWith("Put", StringComparison.OrdinalIgnoreCase) ||
            method.Name.StartsWith("Update", StringComparison.OrdinalIgnoreCase)) return "PUT";
        if (method.Name.StartsWith("Delete", StringComparison.OrdinalIgnoreCase) ||
            method.Name.StartsWith("Remove", StringComparison.OrdinalIgnoreCase)) return "DELETE";
        if (method.Name.StartsWith("Patch", StringComparison.OrdinalIgnoreCase)) return "PATCH";

        return "POST";
    }

    private static bool IsSimpleType(Type type)
    {
        return type.IsPrimitive ||
               type == typeof(string) ||
               type == typeof(decimal) ||
               type == typeof(DateTime) ||
               type == typeof(Guid) ||
               Nullable.GetUnderlyingType(type) != null;
    }
}
