using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
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.Extensions.Options;
using MembershipService.Domain.AggregatesModel.ExperienceAggregate;
using MembershipService.Domain.AggregatesModel.LevelAggregate;
using MembershipService.Domain.AggregatesModel.MemberAggregate;
using MembershipService.Infrastructure;
using MembershipService.Infrastructure.Repositories;

namespace MembershipService.FunctionalTests;

/// <summary>
/// EN: Custom WebApplicationFactory for functional tests with mock auth.
/// VI: WebApplicationFactory tùy chỉnh cho functional tests với mock auth.
/// </summary>
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
    private bool _seeded;

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("Testing");
        
        builder.ConfigureServices(services =>
        {
            // EN: Remove ALL DbContext and EF registrations
            // VI: Xóa TẤT CẢ các đăng ký DbContext và EF
            var descriptorsToRemove = services.Where(
                d => d.ServiceType == typeof(DbContextOptions<MembershipServiceContext>) ||
                     d.ServiceType == typeof(MembershipServiceContext) ||
                     d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true ||
                     d.ServiceType.FullName?.Contains("Npgsql") == true ||
                     d.ServiceType.FullName?.Contains("DbContext") == true)
                .ToList();

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

            // EN: Add in-memory database for testing with unique name per factory
            // VI: Thêm in-memory database để test với tên duy nhất cho mỗi factory
            var databaseName = $"TestDb_{Guid.NewGuid()}";
            services.AddDbContext<MembershipServiceContext>(options =>
            {
                options.UseInMemoryDatabase(databaseName);
                // EN: Suppress transaction warning since in-memory doesn't support transactions
                // VI: Bỏ qua cảnh báo transaction vì in-memory không hỗ trợ transactions
                options.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.InMemoryEventId.TransactionIgnoredWarning));
            });

            // EN: Re-register repositories
            // VI: Đăng ký lại repositories
            services.AddScoped<IMemberRepository, MemberRepository>();
            services.AddScoped<ILevelDefinitionRepository, LevelDefinitionRepository>();
            services.AddScoped<IExperienceTransactionRepository, ExperienceTransactionRepository>();
        });

        builder.ConfigureTestServices(services =>
        {
            // EN: Override authentication with test scheme
            // VI: Override authentication với test scheme
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = "Test";
                options.DefaultChallengeScheme = "Test";
                options.DefaultScheme = "Test";
            })
            .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", options => { });
        });
    }

    /// <summary>
    /// EN: Create client with authenticated user.
    /// VI: Tạo client với user đã xác thực.
    /// </summary>
    public HttpClient CreateAuthenticatedClient(Guid? userId = null)
    {
        var client = CreateClient();
        client.DefaultRequestHeaders.Add("X-Test-User-Id", (userId ?? Guid.NewGuid()).ToString());
        return client;
    }

    /// <summary>
    /// EN: Seed level definitions into database.
    /// VI: Seed level definitions vào database.
    /// </summary>
    public async Task SeedLevelDefinitionsAsync()
    {
        if (_seeded) return;

        using var scope = Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<MembershipServiceContext>();
        
        if (!await db.LevelDefinitions.AnyAsync())
        {
            db.LevelDefinitions.AddRange(
                new LevelDefinition(1, "Bronze", 0, "Starting level", null, "#CD7F32"),
                new LevelDefinition(2, "Silver", 100, "Reach 100 EXP", null, "#C0C0C0"),
                new LevelDefinition(3, "Gold", 300, "Reach 300 EXP", null, "#FFD700"),
                new LevelDefinition(4, "Platinum", 600, "Reach 600 EXP", null, "#E5E4E2"),
                new LevelDefinition(5, "Diamond", 1000, "Reach 1000 EXP", null, "#B9F2FF")
            );
            await db.SaveChangesAsync();
        }
        
        _seeded = true;
    }
}

/// <summary>
/// EN: Test authentication handler for functional tests.
/// VI: Test authentication handler cho functional tests.
/// </summary>
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(
        IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder) : base(options, logger, encoder)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // EN: Check for test user ID header
        // VI: Kiểm tra header test user ID
        if (!Request.Headers.TryGetValue("X-Test-User-Id", out var userIdValues))
        {
            return Task.FromResult(AuthenticateResult.Fail("Missing X-Test-User-Id header"));
        }

        var userId = userIdValues.FirstOrDefault() ?? Guid.NewGuid().ToString();

        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, userId),
            new Claim("sub", userId),
            new Claim("id", userId),
            new Claim(ClaimTypes.Name, "Test User"),
            new Claim(ClaimTypes.Email, "test@example.com"),
            new Claim(ClaimTypes.Role, "Admin")
        };

        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "Test");

        return Task.FromResult(AuthenticateResult.Success(ticket));
    }
}
