using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Caching.Distributed;

using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace CloudGaming.Code.MiddlewareExtend
{
    /// <summary>
    /// Redis 缓存中间件
    /// </summary>
    public class RedisCacheMiddleware
    {
        private readonly RequestDelegate _next;

        private static readonly ConcurrentDictionary<Endpoint, RedisCacheAttribute> _attributeCache = new();

        public RedisCacheMiddleware(RequestDelegate next)
        {
            _next = next;

        }

        public async Task InvokeAsync(HttpContext context,
            IServiceProvider _serviceProvider,
            AppConfig appConfig
            )
        {
            // 检查当前请求是否需要缓存
            var cacheAttribute = GetCacheAttribute(context);
            if (cacheAttribute == null)
            {
                await _next(context);
                return;
            }
            var _cache = appConfig.GetRedisDataBase();

            // 生成缓存键（基于请求路径和查询字符串）
            var cacheKey = GenerateCacheKey(context.Request, appConfig);

            // 尝试从 Redis 缓存中获取数据
            var cachedResponse = await _cache.StringGetAsync(cacheKey);
            if (!cachedResponse.IsNullOrEmpty)
            {
                context.Response.StatusCode = StatusCodes.Status200OK;
                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(cachedResponse);
                return;
            }

            // 捕获原始响应流
            var originalResponseStream = context.Response.Body;

            try
            {
                using (var memoryStream = new MemoryStream())
                {
                    context.Response.Body = memoryStream;

                    // 调用下一个中间件
                    await _next(context);

                    // 将响应流重新定位到起始位置
                    memoryStream.Position = 0;

                    // 读取响应内容
                    var responseBody = await new StreamReader(memoryStream).ReadToEndAsync();

                    await _cache.StringSetAsync(cacheKey, responseBody, cacheAttribute.TimeSpan);

                    // 将内容写回原始响应流
                    memoryStream.Position = 0;
                    await memoryStream.CopyToAsync(originalResponseStream);
                }
            }
            finally
            {
                context.Response.Body = originalResponseStream;
            }
        }

        private RedisCacheAttribute GetCacheAttribute(HttpContext context)
        {
            var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
            if (endpoint == null) return null;

            if (_attributeCache.TryGetValue(endpoint, out var cachedAttribute))
            {
                return cachedAttribute;
            }

            var attribute = endpoint.Metadata.GetMetadata<RedisCacheAttribute>();
            if (attribute != null)
            {
                _attributeCache.TryAdd(endpoint, attribute);
            }

            return attribute;
        }

        private string GenerateCacheKey(HttpRequest request, AppConfig appConfig)
        {
            var appRequestInfo = new AppRequestConfig(request);
            var cacheKey = $"cache:api:{request.Path.Value.Replace('/', '.').TrimStart('.')}:{appRequestInfo.Key}:{(string.IsNullOrEmpty(request.QueryString.Value) ? "default" : request.QueryString)}";
            return cacheKey;
        }
    }

    /// <summary>
    /// Redis 缓存特性类 
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public class RedisCacheAttribute : Attribute
    {
        public TimeSpan TimeSpan { get; }
        public RedisCacheAttribute(int durationInSeconds) => TimeSpan = TimeSpan.FromSeconds(durationInSeconds);
        public RedisCacheAttribute(int hours, int minutes, int seconds) => TimeSpan = new TimeSpan(hours, minutes, seconds);
        public RedisCacheAttribute(int minutes, int seconds) => TimeSpan = new TimeSpan(0, minutes, seconds);
    }
}
