using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Moq;
using ROLAC.API.Data;
using ROLAC.API.DTOs.Users;
using ROLAC.API.Entities;
using ROLAC.API.Services;
using Xunit;

namespace ROLAC.API.Tests.Services;

public class UserManagementServiceTests
{
    private static AppDbContext BuildDb() =>
        new(new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options);

    private static Mock<UserManager<AppUser>> BuildUserManager(
        AppUser?       findResult  = null,
        bool           createOk    = true,
        IList<string>? roles       = null)
    {
        var store = new Mock<IUserStore<AppUser>>();
#pragma warning disable CS8625
        var mgr = new Mock<UserManager<AppUser>>(
            store.Object, null, null, null, null, null, null, null, null);
#pragma warning restore CS8625
        mgr.Setup(m => m.FindByIdAsync(It.IsAny<string>()))
           .ReturnsAsync(findResult);
        mgr.Setup(m => m.FindByEmailAsync(It.IsAny<string>()))
           .ReturnsAsync((AppUser?)null);
        mgr.Setup(m => m.CreateAsync(It.IsAny<AppUser>(), It.IsAny<string>()))
           .ReturnsAsync(createOk ? IdentityResult.Success
                                  : IdentityResult.Failed(new IdentityError { Description = "fail" }));
        mgr.Setup(m => m.AddToRolesAsync(It.IsAny<AppUser>(), It.IsAny<IEnumerable<string>>()))
           .ReturnsAsync(IdentityResult.Success);
        mgr.Setup(m => m.GetRolesAsync(It.IsAny<AppUser>()))
           .ReturnsAsync(roles ?? new List<string> { "member" });
        mgr.Setup(m => m.UpdateAsync(It.IsAny<AppUser>()))
           .ReturnsAsync(IdentityResult.Success);
        mgr.Setup(m => m.RemoveFromRolesAsync(It.IsAny<AppUser>(), It.IsAny<IEnumerable<string>>()))
           .ReturnsAsync(IdentityResult.Success);
        mgr.Setup(m => m.GeneratePasswordResetTokenAsync(It.IsAny<AppUser>()))
           .ReturnsAsync("reset-token");
        mgr.Setup(m => m.ResetPasswordAsync(It.IsAny<AppUser>(), It.IsAny<string>(), It.IsAny<string>()))
           .ReturnsAsync(IdentityResult.Success);
        return mgr;
    }

    // ── CreateAsync ──────────────────────────────────────────────────────────

    [Fact]
    public async Task CreateAsync_ReturnsTempPassword()
    {
        using var db   = BuildDb();
        // Seed a Member so MemberId validation passes
        // Note: InMemory DB requires audit fields — we set them directly
        var member = new Member
        {
            FirstName_en = "A", LastName_en = "B",
            CreatedBy = "system", UpdatedBy = "system",
            CreatedAt = DateTimeOffset.UtcNow, UpdatedAt = DateTimeOffset.UtcNow,
        };
        db.Members.Add(member);
        await db.SaveChangesAsync();

        var mgr = BuildUserManager();
        // Capture the AppUser passed to CreateAsync
        AppUser? created = null;
        mgr.Setup(m => m.CreateAsync(It.IsAny<AppUser>(), It.IsAny<string>()))
           .Callback<AppUser, string>((u, _) => { created = u; u.Id = Guid.NewGuid().ToString(); })
           .ReturnsAsync(IdentityResult.Success);

        // Mock Users queryable to return empty (no existing user for this member)
        mgr.Setup(m => m.Users)
           .Returns(new List<AppUser>().AsQueryable());

        var svc    = new UserManagementService(mgr.Object, db, ROLAC.API.Tests.TestSupport.NullAuditLogger.Instance);
        var result = await svc.CreateAsync(new CreateUserRequest
        {
            MemberId = member.Id,
            Email    = "test@rolac.org",
            Roles    = ["member"],
        });

        Assert.False(string.IsNullOrEmpty(result.TempPassword));
        Assert.Equal(12, result.TempPassword.Length);
        Assert.NotNull(created);
        Assert.Equal(member.Id, created!.MemberId);
    }

    [Fact]
    public async Task CreateAsync_Throws_WhenMemberNotFound()
    {
        using var db = BuildDb();
        var mgr      = BuildUserManager();
        mgr.Setup(m => m.Users)
           .Returns(new List<AppUser>().AsQueryable());
        var svc      = new UserManagementService(mgr.Object, db, ROLAC.API.Tests.TestSupport.NullAuditLogger.Instance);

        await Assert.ThrowsAsync<InvalidOperationException>(() =>
            svc.CreateAsync(new CreateUserRequest
                { MemberId = 9999, Email = "x@y.com", Roles = ["member"] }));
    }

    [Fact]
    public async Task CreateAsync_Throws_WhenMemberAlreadyHasUser()
    {
        using var db = BuildDb();
        var member   = new Member
        {
            FirstName_en = "A", LastName_en = "B",
            CreatedBy = "system", UpdatedBy = "system",
            CreatedAt = DateTimeOffset.UtcNow, UpdatedAt = DateTimeOffset.UtcNow,
        };
        db.Members.Add(member);
        await db.SaveChangesAsync();

        var existingUser = new AppUser
        {
            Id       = Guid.NewGuid().ToString(),
            UserName = "existing@test.com",
            Email    = "existing@test.com",
            MemberId = member.Id,
        };
        db.Users.Add(existingUser);
        await db.SaveChangesAsync();

        var mgr = BuildUserManager();
        // The service checks _userManager.Users — we need to return the existing user
        mgr.Setup(m => m.Users)
           .Returns(new List<AppUser> { existingUser }.AsQueryable());
        var svc = new UserManagementService(mgr.Object, db, ROLAC.API.Tests.TestSupport.NullAuditLogger.Instance);

        await Assert.ThrowsAsync<InvalidOperationException>(() =>
            svc.CreateAsync(new CreateUserRequest
                { MemberId = member.Id, Email = "new@test.com", Roles = ["member"] }));
    }

    // ── DeactivateAsync ──────────────────────────────────────────────────────

    [Fact]
    public async Task DeactivateAsync_SetsIsActiveFalse()
    {
        using var db = BuildDb();
        var user     = new AppUser
            { Id = "u1", UserName = "a@b.com", Email = "a@b.com", IsActive = true };
        var mgr      = BuildUserManager(findResult: user);
        var svc      = new UserManagementService(mgr.Object, db, ROLAC.API.Tests.TestSupport.NullAuditLogger.Instance);

        await svc.DeactivateAsync("u1");

        Assert.False(user.IsActive);
        Assert.Equal(DateTimeOffset.MaxValue, user.LockoutEnd);
    }

    [Fact]
    public async Task DeactivateAsync_ThrowsKeyNotFound_WhenUserMissing()
    {
        using var db = BuildDb();
        var mgr      = BuildUserManager(findResult: null);
        var svc      = new UserManagementService(mgr.Object, db, ROLAC.API.Tests.TestSupport.NullAuditLogger.Instance);

        await Assert.ThrowsAsync<KeyNotFoundException>(() => svc.DeactivateAsync("missing"));
    }

    // ── ResetPasswordAsync ───────────────────────────────────────────────────

    [Fact]
    public async Task ResetPasswordAsync_ReturnsNewTempPassword()
    {
        using var db = BuildDb();
        var user     = new AppUser { Id = "u1", UserName = "a@b.com", Email = "a@b.com" };
        var mgr      = BuildUserManager(findResult: user);
        var svc      = new UserManagementService(mgr.Object, db, ROLAC.API.Tests.TestSupport.NullAuditLogger.Instance);

        var pwd = await svc.ResetPasswordAsync("u1");

        Assert.Equal(12, pwd.Length);
    }
}
