namespace DigitalData.Core.Tests.Infrastructure;

using DigitalData.Core.Infrastructure;
using DigitalData.Core.Tests.Mock;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
using DigitalData.Core.Infrastructure.AutoMapper;
using DigitalData.Core.Application.Interfaces.Repository;

public class DbRepositoryTests
{
    private IHost _host;

    private IRepository<User> _userRepo;

    [SetUp]
    public void Setup()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddDbContext<MockDbContext>(opt => opt.UseInMemoryDatabase("MockDB"));

        builder.Services.AddDbRepository<MockDbContext, User>(context => context.Users).UseAutoMapper(typeof(UserCreateDto), typeof(UserReadDto), typeof(UserBase));

        builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());

        _host = builder.Build();

        _userRepo = _host.Services.GetRequiredService<IRepository<User>>();
    }

    [TearDown]
    public void TearDown()
    {
        if (_host is IDisposable disposableHost)
            disposableHost.Dispose();
    }

    [TestCase(true, TestName = "WhenGivenMultipleUsers")]
    [TestCase(false, TestName = "WhenGivenSingleUser")]
    public void CreateAsync_ShouldNotThrow(bool multiple)
    {
        // Arrange
        var faker = Fake.CreateUserFaker();

        // Act & Assert
        if (multiple)
            Assert.DoesNotThrowAsync(async () => await _userRepo.CreateAsync(faker.Generate(Random.Shared.Next(1, 10))));
        else
            Assert.DoesNotThrowAsync(async () => await _userRepo.CreateAsync(faker.Generate()));
    }

    [TestCase(true, TestName = "WhenDtoUsed")]
    [TestCase(false, TestName = "WhenEntityUsed")]
    public async Task ReadAsync_ShouldReturnCreated(bool useDto)
    {
        // Act
        var createdUser = useDto
            ? await _userRepo.CreateAsync(Fake.UserCreateDto)
            : await _userRepo.CreateAsync(Fake.User);

        var readUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);

        // Assert
        Assert.Multiple(() =>
        {
            Assert.That(readUser, Is.Not.Null);
            Assert.That(readUser, Is.EqualTo(createdUser));
        });
    }

    [Test]
    public async Task ReadAsync_ShouldReturnUpdated()
    {
        // Arrange
        var createdUser = await _userRepo.CreateAsync(Fake.UserCreateDto);
        var userUpdateDto = new UserUpdateDto() { Age = 10, Email = "Bar", FirstName = "Foo" };

        // Act
        await _userRepo.UpdateAsync(userUpdateDto, u => u.Id == createdUser!.Id);
        var upToDateUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser!.Id);

        // Assert
        Assert.Multiple(() =>
        {
            Assert.That(upToDateUser, Is.Not.Null);
            Assert.That(upToDateUser?.FirstName, Is.EqualTo(userUpdateDto.FirstName));
            Assert.That(upToDateUser?.Email, Is.EqualTo(userUpdateDto.Email));
            Assert.That(upToDateUser?.Age, Is.EqualTo(userUpdateDto.Age));
        });
    }

    [Test]
    public async Task ReadAsync_ShouldNotReturnDeleted()
    {
        // Arrange
        var createdUser = await _userRepo.CreateAsync(Fake.UserCreateDto);
        var readUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);

        // Act
        await _userRepo.DeleteAsync(u => u.Id == createdUser.Id);
        var deletedUser = await _userRepo.ReadFirstOrDefaultAsync(u => u.Id == createdUser.Id);
        
        // Assert
        Assert.Multiple(() =>
        {
            Assert.That(readUser, Is.Not.Null);
            Assert.That(deletedUser, Is.Null);
        });
    }
}
