using System.Text.Json;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace MiGu.Server.OpenApi;

/// <summary>
/// 将 SimpleLite EmbedIO WebApi 的 OpenAPI 描述合并进 MiGu.Server Swagger 文档。
/// 源文件：Simple/SimpleLite/Docs/openapi/simplelite-projection.json
/// </summary>
public sealed class SimpleLiteOpenApiDocumentFilter : IDocumentFilter
{
    private static readonly Dictionary<string, OperationType> VerbMap = new(StringComparer.OrdinalIgnoreCase)
    {
        ["get"] = OperationType.Get,
        ["post"] = OperationType.Post,
        ["put"] = OperationType.Put,
        ["patch"] = OperationType.Patch,
        ["delete"] = OperationType.Delete,
        ["head"] = OperationType.Head,
        ["options"] = OperationType.Options,
        ["trace"] = OperationType.Trace
    };

    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        var json = TryLoadJson();
        if (json == null) return;

        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        if (root.TryGetProperty("tags", out var tagsEl) && tagsEl.ValueKind == JsonValueKind.Array)
        {
            swaggerDoc.Tags ??= new List<OpenApiTag>();
            foreach (var tag in tagsEl.EnumerateArray())
            {
                if (!tag.TryGetProperty("name", out var nameEl)) continue;
                var name = nameEl.GetString();
                if (string.IsNullOrEmpty(name) || swaggerDoc.Tags.Any(t => t.Name == name)) continue;
                var desc = tag.TryGetProperty("description", out var d) ? d.GetString() : null;
                swaggerDoc.Tags.Add(new OpenApiTag { Name = name, Description = desc });
            }
        }

        if (!root.TryGetProperty("paths", out var pathsEl)) return;

        foreach (var pathProp in pathsEl.EnumerateObject())
        {
            var fullPath = pathProp.Name.StartsWith("/api/sl", StringComparison.Ordinal)
                ? pathProp.Name
                : "/api/sl" + pathProp.Name;

            var pathItem = new OpenApiPathItem();
            foreach (var opProp in pathProp.Value.EnumerateObject())
            {
                if (!VerbMap.TryGetValue(opProp.Name, out var verb)) continue;
                pathItem.Operations[verb] = ParseOperation(opProp.Value);
            }

            if (pathItem.Operations.Count > 0)
                swaggerDoc.Paths[fullPath] = pathItem;
        }
    }

    private static OpenApiOperation ParseOperation(JsonElement el)
    {
        var op = new OpenApiOperation
        {
            Summary = el.TryGetProperty("summary", out var s) ? s.GetString() : null,
            Description = el.TryGetProperty("description", out var d) ? d.GetString() : null
        };

        if (el.TryGetProperty("tags", out var tags) && tags.ValueKind == JsonValueKind.Array)
        {
            foreach (var t in tags.EnumerateArray())
            {
                var name = t.GetString();
                if (!string.IsNullOrEmpty(name))
                    op.Tags.Add(new OpenApiTag { Name = name });
            }
        }

        if (el.TryGetProperty("parameters", out var parameters) && parameters.ValueKind == JsonValueKind.Array)
        {
            foreach (var p in parameters.EnumerateArray())
            {
                var param = new OpenApiParameter
                {
                    Name = p.TryGetProperty("name", out var n) ? n.GetString() : null,
                    In = p.TryGetProperty("in", out var loc) ? ParameterLocationFrom(loc.GetString()) : null,
                    Required = p.TryGetProperty("required", out var req) && req.GetBoolean(),
                    Description = p.TryGetProperty("description", out var pd) ? pd.GetString() : null
                };
                if (p.TryGetProperty("schema", out var schema))
                    param.Schema = ParseSchema(schema);
                op.Parameters.Add(param);
            }
        }

        if (el.TryGetProperty("requestBody", out var body))
            op.RequestBody = ParseRequestBody(body);

        if (el.TryGetProperty("responses", out var responses))
        {
            foreach (var resp in responses.EnumerateObject())
            {
                op.Responses[resp.Name] = new OpenApiResponse
                {
                    Description = resp.Value.TryGetProperty("description", out var rd)
                        ? rd.GetString() ?? ""
                        : ""
                };
            }
        }

        return op;
    }

    private static OpenApiRequestBody? ParseRequestBody(JsonElement el)
    {
        if (!el.TryGetProperty("content", out var content)) return null;
        var body = new OpenApiRequestBody();
        foreach (var ct in content.EnumerateObject())
        {
            var media = new OpenApiMediaType();
            if (ct.Value.TryGetProperty("schema", out var schema))
                media.Schema = ParseSchema(schema);
            body.Content[ct.Name] = media;
        }
        return body.Content.Count > 0 ? body : null;
    }

    private static OpenApiSchema ParseSchema(JsonElement el)
    {
        var schema = new OpenApiSchema();
        if (el.TryGetProperty("type", out var t)) schema.Type = t.GetString();
        if (el.TryGetProperty("description", out var d)) schema.Description = d.GetString();
        if (el.TryGetProperty("$ref", out var r))
        {
            var refId = r.GetString()?.TrimStart('#', '/');
            if (!string.IsNullOrEmpty(refId))
                schema.Reference = new OpenApiReference { Id = refId, Type = ReferenceType.Schema };
        }
        return schema;
    }

    private static ParameterLocation? ParameterLocationFrom(string? loc) => loc switch
    {
        "query" => ParameterLocation.Query,
        "path" => ParameterLocation.Path,
        "header" => ParameterLocation.Header,
        "cookie" => ParameterLocation.Cookie,
        _ => null
    };

    private static string? TryLoadJson()
    {
        foreach (var candidate in ResolveCandidatePaths())
        {
            try
            {
                if (File.Exists(candidate))
                    return File.ReadAllText(candidate);
            }
            catch { /* next */ }
        }
        return null;
    }

    private static IEnumerable<string> ResolveCandidatePaths()
    {
        var roots = new[] { AppContext.BaseDirectory, Directory.GetCurrentDirectory() }
            .Distinct(StringComparer.OrdinalIgnoreCase);

        foreach (var root in roots)
        {
            yield return Path.GetFullPath(Path.Combine(root, "OpenApi", "simplelite-projection.json"));
            yield return Path.GetFullPath(Path.Combine(root, "..", "..", "..", "Simple", "SimpleLite", "Docs", "openapi", "simplelite-projection.json"));
            yield return Path.GetFullPath(Path.Combine(root, "..", "..", "Simple", "SimpleLite", "Docs", "openapi", "simplelite-projection.json"));
        }
    }
}
