using Microsoft.AspNetCore.Identity;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Domain.Identity;
using SolutionErp.Infrastructure.Persistence;

namespace SolutionErp.Infrastructure.Tests.Common;

// Identity-aware fixture cho tests cần UserManager + RoleManager.
//
// Tại sao tách khỏi SqliteDbFixture?
//   - SqliteDbFixture chỉ EF + DbContext (đủ cho code generator tests).
//   - Service tests cần Identity stack: UserManager.FindByIdAsync, GetRolesAsync,
//     CreateAsync, AddToRolesAsync — phụ thuộc IUserStore + IRoleStore + hashers
//     + validators registered qua AddIdentityCore + AddRoles + AddEntityFrameworkStores.
//   - Single connection mỗi fixture → DB persists across UserManager.Save calls.
//
// Pattern: ServiceProvider build từ AddIdentityCore. Test gọi GetRequired để
// resolve UserManager, DbContext, etc. EnsureCreated() build schema từ model
// (skip migrations vì test isolated).
public sealed class IdentityFixture : IDisposable
{
    private readonly SqliteConnection _connection;
    private readonly ServiceProvider _root;
    public IServiceProvider Services { get; }       // scoped to single test scope (shared across tests in class)

    public IdentityFixture()
    {
        _connection = new SqliteConnection("DataSource=:memory:");
        _connection.Open();

        var services = new ServiceCollection();
        services.AddLogging();

        // Manual options + factory để inject TestApplicationDbContext qua type
        // ApplicationDbContext (Identity EF stores expect base type).
        var connection = _connection;
        services.AddScoped<ApplicationDbContext>(_ =>
        {
            var options = new DbContextOptionsBuilder<ApplicationDbContext>()
                .UseSqlite(connection)
                .EnableSensitiveDataLogging()
                .Options;
            return new TestApplicationDbContext(options);
        });
        services.AddScoped<TestApplicationDbContext>(sp =>
            (TestApplicationDbContext)sp.GetRequiredService<ApplicationDbContext>());
        services.AddScoped<IApplicationDbContext>(sp =>
            sp.GetRequiredService<TestApplicationDbContext>());

        services.AddIdentityCore<User>(opt =>
            {
                opt.Password.RequireDigit = false;
                opt.Password.RequireLowercase = false;
                opt.Password.RequireUppercase = false;
                opt.Password.RequireNonAlphanumeric = false;
                opt.Password.RequiredLength = 4;
            })
            .AddRoles<Role>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

        _root = services.BuildServiceProvider();
        Services = _root.CreateScope().ServiceProvider;

        var ctx = Services.GetRequiredService<TestApplicationDbContext>();
        ctx.Database.EnsureCreated();
    }

    // Helper: tạo user + assign roles + gán DepartmentId + PositionLevel. Reuse trong nhiều test.
    public async Task<User> CreateUserAsync(
        string email,
        string fullName,
        Guid? departmentId,
        string[] roles,
        bool canBypassReview = false,
        PositionLevel? positionLevel = null)
    {
        var um = Services.GetRequiredService<UserManager<User>>();
        var rm = Services.GetRequiredService<RoleManager<Role>>();

        // Ensure roles exist (idempotent).
        foreach (var role in roles)
        {
            if (!await rm.RoleExistsAsync(role))
                await rm.CreateAsync(new Role { Id = Guid.NewGuid(), Name = role });
        }

        var user = new User
        {
            Id = Guid.NewGuid(),
            UserName = email,
            Email = email,
            EmailConfirmed = true,
            FullName = fullName,
            DepartmentId = departmentId,
            CanBypassReview = canBypassReview,
            PositionLevel = positionLevel,
            IsActive = true,
        };
        var created = await um.CreateAsync(user, "Test@123");
        if (!created.Succeeded)
            throw new InvalidOperationException("CreateUserAsync failed: " + string.Join(",", created.Errors.Select(e => e.Description)));
        if (roles.Length > 0)
            await um.AddToRolesAsync(user, roles);
        return user;
    }

    public void Dispose()
    {
        _root.Dispose();
        _connection.Dispose();
    }
}
