using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using IamService.Infrastructure;
using IamService.Infrastructure.Caching;
using IamService.Domain.AggregatesModel.UserAggregate;
using IamService.Domain.AggregatesModel.RoleAggregate;
using Microsoft.AspNetCore.Identity;
using StackExchange.Redis;

namespace IamService.FunctionalTests;

/// <summary>
/// EN: Custom WebApplicationFactory for functional tests with Duende IdentityServer.
/// VI: WebApplicationFactory tùy chỉnh cho functional tests với Duende IdentityServer.
/// </summary>
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
    private readonly string _databaseName = "TestDatabase_" + Guid.NewGuid().ToString();

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("Testing");

        // EN: Configure services BEFORE the app configures itself
        // VI: Cấu hình services TRƯỚC KHI app tự cấu hình
        builder.ConfigureServices(services =>
        {
            // EN: Remove ALL existing DbContext registrations
            // VI: Xóa TẤT CẢ các đăng ký DbContext hiện có
            RemoveExistingDbContextRegistrations(services);
            
            // EN: Remove Redis-related registrations
            // VI: Xóa các đăng ký liên quan đến Redis
            RemoveRedisRegistrations(services);

            // EN: Add mock cache service for testing
            // VI: Thêm mock cache service để test
            services.AddSingleton<ICacheService, InMemoryCacheService>();

            // EN: Add in-memory database for testing
            // VI: Thêm in-memory database để test
            services.AddDbContext<IamServiceContext>(options =>
            {
                options.UseInMemoryDatabase(_databaseName);
                options.EnableSensitiveDataLogging();
            });

            // EN: Set logging level for debugging tests
            // VI: Đặt mức logging để debug tests
            services.AddLogging(logging =>
            {
                logging.SetMinimumLevel(LogLevel.Warning);
                logging.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Warning);
                logging.AddFilter("Duende.IdentityServer", LogLevel.Warning);
            });
        });

        // EN: Configure services AFTER the app is configured - this runs after Program.cs
        // VI: Cấu hình services SAU KHI app được cấu hình - chạy sau Program.cs
        builder.ConfigureTestServices(services =>
        {
            // EN: Remove JWT Bearer and add custom test authentication handler
            // VI: Xóa JWT Bearer và thêm custom test authentication handler
            
            // EN: Override the authentication scheme options to use our test handler
            // VI: Override authentication scheme options để sử dụng test handler
            services.Configure<AuthenticationOptions>(options =>
            {
                // EN: Keep Bearer as default but use our handler
                // VI: Giữ Bearer là mặc định nhưng sử dụng handler của chúng ta
            });

            services.PostConfigure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                // EN: Configure minimal validation
                // VI: Cấu hình validation tối thiểu
                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = false,
                    ValidateIssuerSigningKey = false,
                    RequireSignedTokens = false,
                    // EN: ASP.NET Core 8+ requires JsonWebToken, not JwtSecurityToken
                    // VI: ASP.NET Core 8+ yêu cầu JsonWebToken, không phải JwtSecurityToken
                    SignatureValidator = (token, parameters) => 
                        new Microsoft.IdentityModel.JsonWebTokens.JsonWebToken(token)
                };
                
                // EN: Use static configuration  
                // VI: Sử dụng static configuration
                options.RequireHttpsMetadata = false;
                var config = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration
                {
                    Issuer = "http://localhost"
                };
                options.ConfigurationManager = new Microsoft.IdentityModel.Protocols.StaticConfigurationManager<Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration>(config);
                
                // EN: Event to help debug authentication issues
                // VI: Event để giúp debug authentication issues
                options.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = context =>
                    {
                        var logger = context.HttpContext.RequestServices.GetService<ILoggerFactory>()?.CreateLogger("JwtBearerEvents");
                        logger?.LogError(context.Exception, "JWT authentication failed: {Message}", context.Exception.Message);
                        return Task.CompletedTask;
                    },
                    OnMessageReceived = context =>
                    {
                        // EN: Ensure the token is extracted correctly
                        // VI: Đảm bảo token được extract đúng
                        var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
                        if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                        {
                            context.Token = authHeader.Substring("Bearer ".Length).Trim();
                        }
                        return Task.CompletedTask;
                    }
                };
            });
        });
    }

    private static void RemoveExistingDbContextRegistrations(IServiceCollection services)
    {
        // EN: Remove all DbContext-related registrations
        // VI: Xóa tất cả các đăng ký liên quan đến DbContext
        var descriptorsToRemove = services.Where(d =>
            d.ServiceType == typeof(DbContextOptions<IamServiceContext>) ||
            d.ServiceType == typeof(IamServiceContext) ||
            d.ServiceType == typeof(DbContextOptions) ||
            d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true ||
            d.ImplementationType?.FullName?.Contains("Npgsql") == true)
            .ToList();

        foreach (var descriptor in descriptorsToRemove)
        {
            services.Remove(descriptor);
        }

        services.RemoveAll(typeof(DbContextOptions));
        services.RemoveAll(typeof(DbContextOptions<IamServiceContext>));
    }

    private static void RemoveRedisRegistrations(IServiceCollection services)
    {
        // EN: Remove Redis-related registrations
        // VI: Xóa các đăng ký liên quan đến Redis
        var redisDescriptors = services.Where(d =>
            d.ServiceType == typeof(IConnectionMultiplexer) ||
            d.ServiceType == typeof(ICacheService) ||
            d.ImplementationType?.FullName?.Contains("Redis") == true ||
            d.ServiceType.FullName?.Contains("Redis") == true)
            .ToList();

        foreach (var descriptor in redisDescriptors)
        {
            services.Remove(descriptor);
        }
    }

    /// <summary>
    /// EN: Ensure database is created after host is built
    /// VI: Đảm bảo database được tạo sau khi host được build
    /// </summary>
    protected override void ConfigureClient(HttpClient client)
    {
        base.ConfigureClient(client);

        // EN: Create the database when the first client is created
        // VI: Tạo database khi client đầu tiên được tạo
        using var scope = Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<IamServiceContext>();
        db.Database.EnsureCreated();
    }
}

/// <summary>
/// EN: In-memory cache service for testing
/// VI: Cache service in-memory cho testing
/// </summary>
public class InMemoryCacheService : ICacheService
{
    private readonly Dictionary<string, (object Value, DateTime? Expiry)> _cache = new();
    private readonly HashSet<string> _blacklist = new();

    public Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default) where T : class
    {
        if (_cache.TryGetValue(key, out var entry))
        {
            if (entry.Expiry == null || entry.Expiry > DateTime.UtcNow)
            {
                return Task.FromResult((T?)entry.Value);
            }
            _cache.Remove(key);
        }
        return Task.FromResult<T?>(null);
    }

    public Task<string?> GetStringAsync(string key, CancellationToken cancellationToken = default)
    {
        if (_cache.TryGetValue(key, out var entry))
        {
            if (entry.Expiry == null || entry.Expiry > DateTime.UtcNow)
            {
                return Task.FromResult((string?)entry.Value);
            }
            _cache.Remove(key);
        }
        return Task.FromResult<string?>(null);
    }

    public Task SetAsync<T>(string key, T value, TimeSpan? expiration = null, CancellationToken cancellationToken = default) where T : class
    {
        var expiry = expiration.HasValue ? DateTime.UtcNow.Add(expiration.Value) : (DateTime?)null;
        _cache[key] = (value, expiry);
        return Task.CompletedTask;
    }

    public Task SetStringAsync(string key, string value, TimeSpan? expiration = null, CancellationToken cancellationToken = default)
    {
        var expiry = expiration.HasValue ? DateTime.UtcNow.Add(expiration.Value) : (DateTime?)null;
        _cache[key] = (value, expiry);
        return Task.CompletedTask;
    }

    public Task RemoveAsync(string key, CancellationToken cancellationToken = default)
    {
        _cache.Remove(key);
        return Task.CompletedTask;
    }

    public Task RemoveByPatternAsync(string pattern, CancellationToken cancellationToken = default)
    {
        var keysToRemove = _cache.Keys.Where(k => k.Contains(pattern.Replace("*", ""))).ToList();
        foreach (var key in keysToRemove)
        {
            _cache.Remove(key);
        }
        return Task.CompletedTask;
    }

    public Task<bool> ExistsAsync(string key, CancellationToken cancellationToken = default)
    {
        if (_cache.TryGetValue(key, out var entry))
        {
            if (entry.Expiry == null || entry.Expiry > DateTime.UtcNow)
            {
                return Task.FromResult(true);
            }
            _cache.Remove(key);
        }
        return Task.FromResult(false);
    }

    public async Task<T?> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null, CancellationToken cancellationToken = default) where T : class
    {
        var cached = await GetAsync<T>(key, cancellationToken);
        if (cached != null) return cached;

        var value = await factory();
        await SetAsync(key, value, expiration, cancellationToken);
        return value;
    }

    public Task BlacklistAsync(string key, TimeSpan expiration, CancellationToken cancellationToken = default)
    {
        _blacklist.Add(key);
        return Task.CompletedTask;
    }

    public Task<bool> IsBlacklistedAsync(string key, CancellationToken cancellationToken = default)
    {
        return Task.FromResult(_blacklist.Contains(key));
    }
}
