using ColaFlow.Modules.Identity.Domain.Aggregates.Tenants;
using ColaFlow.Modules.Identity.Domain.Aggregates.Users;
using ColaFlow.Modules.Identity.Infrastructure.Persistence;
using ColaFlow.Modules.Identity.Infrastructure.Services;
using FluentAssertions;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Moq;

namespace ColaFlow.Modules.Identity.Infrastructure.Tests.Persistence;

public class GlobalQueryFilterTests : IDisposable
{
    private readonly Mock<ITenantContext> _mockTenantContext;
    private readonly Mock<IMediator> _mockMediator;
    private readonly Mock<IHostEnvironment> _mockEnvironment;
    private readonly Mock<ILogger<IdentityDbContext>> _mockLogger;
    private readonly IdentityDbContext _context;

    public GlobalQueryFilterTests()
    {
        var options = new DbContextOptionsBuilder<IdentityDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        _mockTenantContext = new Mock<ITenantContext>();
        _mockMediator = new Mock<IMediator>();
        _mockEnvironment = new Mock<IHostEnvironment>();
        _mockEnvironment.Setup(e => e.EnvironmentName).Returns("Development");
        _mockLogger = new Mock<ILogger<IdentityDbContext>>();
        _context = new IdentityDbContext(options, _mockTenantContext.Object, _mockMediator.Object, _mockEnvironment.Object, _mockLogger.Object);
    }

    [Fact]
    public async Task GlobalQueryFilter_ShouldFilterByTenant()
    {
        // Arrange - Create 2 users from different tenants
        var tenant1Id = TenantId.CreateUnique();
        var tenant2Id = TenantId.CreateUnique();

        // Setup mock to filter for tenant1
        var mockTenantContext = new Mock<ITenantContext>();
        mockTenantContext.Setup(x => x.IsSet).Returns(true);
        mockTenantContext.Setup(x => x.TenantId).Returns(tenant1Id);

        var options = new DbContextOptionsBuilder<IdentityDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        var mockMediator = new Mock<IMediator>();
        var mockEnvironment = new Mock<IHostEnvironment>();
        mockEnvironment.Setup(e => e.EnvironmentName).Returns("Development");
        var mockLogger = new Mock<ILogger<IdentityDbContext>>();
        using var context = new IdentityDbContext(options, mockTenantContext.Object, mockMediator.Object, mockEnvironment.Object, mockLogger.Object);

        var user1 = User.CreateLocal(
            tenant1Id,
            Email.Create("user1@tenant1.com"),
            "password123",
            FullName.Create("User One"));

        var user2 = User.CreateLocal(
            tenant2Id,
            Email.Create("user2@tenant2.com"),
            "password123",
            FullName.Create("User Two"));

        await context.Users.AddAsync(user1);
        await context.Users.AddAsync(user2);
        await context.SaveChangesAsync();

        // Act - Query users (should be filtered by tenant1)
        var filteredUsers = await context.Users.ToListAsync();

        // Assert - Should only return tenant1's user
        filteredUsers.Should().HaveCount(1);
        filteredUsers[0].Email.Value.Should().Be("user1@tenant1.com");
    }

    [Fact]
    public async Task WithoutTenantFilter_ShouldReturnAllUsers()
    {
        // Arrange - Create 2 users from different tenants
        var tenant1Id = TenantId.CreateUnique();
        var tenant2Id = TenantId.CreateUnique();

        // Setup mock to filter for tenant1
        var mockTenantContext = new Mock<ITenantContext>();
        mockTenantContext.Setup(x => x.IsSet).Returns(true);
        mockTenantContext.Setup(x => x.TenantId).Returns(tenant1Id);

        var options = new DbContextOptionsBuilder<IdentityDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        var mockMediator = new Mock<IMediator>();
        var mockEnvironment = new Mock<IHostEnvironment>();
        mockEnvironment.Setup(e => e.EnvironmentName).Returns("Development");
        var mockLogger = new Mock<ILogger<IdentityDbContext>>();
        using var context = new IdentityDbContext(options, mockTenantContext.Object, mockMediator.Object, mockEnvironment.Object, mockLogger.Object);

        var user1 = User.CreateLocal(tenant1Id, Email.Create("admin@tenant1.com"), "pass", FullName.Create("Admin One"));
        var user2 = User.CreateLocal(tenant2Id, Email.Create("admin@tenant2.com"), "pass", FullName.Create("Admin Two"));

        await context.Users.AddAsync(user1);
        await context.Users.AddAsync(user2);
        await context.SaveChangesAsync();

        // Act - Use WithoutTenantFilter to bypass filter
        var allUsers = await context.WithoutTenantFilter<User>().ToListAsync();

        // Assert - Should return all users
        allUsers.Should().HaveCount(2);
    }

    [Fact]
    public async Task GlobalQueryFilter_ShouldNotFilter_WhenTenantContextNotSet()
    {
        // Arrange - Tenant context not set
        var tenant1Id = TenantId.CreateUnique();
        var tenant2Id = TenantId.CreateUnique();

        var mockTenantContext = new Mock<ITenantContext>();
        mockTenantContext.Setup(x => x.IsSet).Returns(false); // NOT set

        var options = new DbContextOptionsBuilder<IdentityDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        var mockMediator = new Mock<IMediator>();
        var mockEnvironment = new Mock<IHostEnvironment>();
        mockEnvironment.Setup(e => e.EnvironmentName).Returns("Development");
        var mockLogger = new Mock<ILogger<IdentityDbContext>>();
        using var context = new IdentityDbContext(options, mockTenantContext.Object, mockMediator.Object, mockEnvironment.Object, mockLogger.Object);

        var user1 = User.CreateLocal(tenant1Id, Email.Create("user1@test.com"), "pass", FullName.Create("User One"));
        var user2 = User.CreateLocal(tenant2Id, Email.Create("user2@test.com"), "pass", FullName.Create("User Two"));

        await context.Users.AddAsync(user1);
        await context.Users.AddAsync(user2);
        await context.SaveChangesAsync();

        // Act - Query without tenant filter (because IsSet = false)
        var allUsers = await context.Users.ToListAsync();

        // Assert - Should return all users (no filtering)
        allUsers.Should().HaveCount(2);
    }

    [Fact]
    public async Task UserRepository_ShouldRespectTenantFilter()
    {
        // Arrange
        var tenant1Id = TenantId.CreateUnique();
        var tenant2Id = TenantId.CreateUnique();

        var mockTenantContext = new Mock<ITenantContext>();
        mockTenantContext.Setup(x => x.IsSet).Returns(true);
        mockTenantContext.Setup(x => x.TenantId).Returns(tenant1Id);

        var options = new DbContextOptionsBuilder<IdentityDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        var mockMediator = new Mock<IMediator>();
        var mockEnvironment = new Mock<IHostEnvironment>();
        mockEnvironment.Setup(e => e.EnvironmentName).Returns("Development");
        var mockLogger = new Mock<ILogger<IdentityDbContext>>();
        using var context = new IdentityDbContext(options, mockTenantContext.Object, mockMediator.Object, mockEnvironment.Object, mockLogger.Object);

        var user1 = User.CreateLocal(tenant1Id, Email.Create("john@tenant1.com"), "pass", FullName.Create("John Doe"));
        var user2 = User.CreateLocal(tenant2Id, Email.Create("jane@tenant2.com"), "pass", FullName.Create("Jane Doe"));

        await context.Users.AddAsync(user1);
        await context.Users.AddAsync(user2);
        await context.SaveChangesAsync();

        // Act
        var repository = new ColaFlow.Modules.Identity.Infrastructure.Persistence.Repositories.UserRepository(context);
        var retrievedUser = await repository.GetByIdAsync(UserId.Create(user1.Id));

        // Assert - Should find user1 (same tenant)
        retrievedUser.Should().NotBeNull();
        retrievedUser!.Email.Value.Should().Be("john@tenant1.com");

        // Trying to get user2 (from different tenant) should return null due to filter
        var user2Attempt = await repository.GetByIdAsync(UserId.Create(user2.Id));
        user2Attempt.Should().BeNull();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}
