using ApplicationLayer.InfrastructureServicesInterfaces;
using ApplicationLayer.Services.Applicants.NeededServices;
using FluentAssertions;
using Infrastructure.Database;
using Infrastructure.Database.Applicants.Repositories;
using Infrastructure.Database.Applicants.Repositories.Exceptions;
using VisaApi.Fakers.Applicants;
using VisaApi.Fakers.Users;
using VisaApi.Services;
using Xunit;
namespace VisaApi.Tests.Infrastructure.Database.Repositories
{
    [Collection(Collections.ContextUsingTestCollection)]
    public class ApplicantsRepositoryTests
    {
        private readonly static UserFaker userFaker = new();
        private readonly static ApplicantFaker applicantFaker = new(GetDateTimeProvider());
        ///  Returns  
        ///  Database context 
        /// Repository
        private static IApplicantsRepository GetRepository(DbContext context)
            => new ApplicantsRepository(context, context);
        ///  Returns  
        private static IDateTimeProvider GetDateTimeProvider() => new TestDateTimeProvider();
        /// 
        /// Test for  method that should throw exception for not existing entity
        /// 
        [Fact]
        private async Task FindByUserIdForNotExistingShouldThrow()
        {
            await using var context = InMemoryContextProvider.GetDbContext();
            var repository = GetRepository(context);
            ApplicantNotFoundByUserIdException? result = null;
            try
            {
                await repository.FindByUserIdAsync(Guid.NewGuid(), CancellationToken.None);
            }
            catch (Exception e)
            {
                result = e as ApplicantNotFoundByUserIdException;
            }
            result.Should().NotBeNull();
        }
        /// 
        /// Test for  method that should return existing entity
        /// 
        [Fact]
        private async Task FindByUserIdForExistingShouldReturnApplicant()
        {
            await using var context = InMemoryContextProvider.GetDbContext();
            var repository = GetRepository(context);
            var user = userFaker.Generate();
            var applicant = applicantFaker.Generate();
            applicant.UserId = user.Id;
            await context.AddAsync(user);
            await repository.AddAsync(applicant, CancellationToken.None);
            await context.SaveChangesAsync();
            var result = await repository.FindByUserIdAsync(user.Id, CancellationToken.None);
            result.Should().BeEquivalentTo(applicant);
        }
        /// 
        /// Test for  method that should throw exception for not existing entity
        /// 
        [Fact]
        private async Task GetApplicantIdByUserIdForNotExistingShouldThrow()
        {
            await using var context = InMemoryContextProvider.GetDbContext();
            var repository = GetRepository(context);
            ApplicantNotFoundByUserIdException? result = null;
            try
            {
                await repository.GetApplicantIdByUserId(Guid.NewGuid(), CancellationToken.None);
            }
            catch (Exception e)
            {
                result = e as ApplicantNotFoundByUserIdException;
            }
            result.Should().NotBeNull();
        }
        /// 
        /// Test for  method that should return existing entity's identifier
        /// 
        [Fact]
        private async Task GetApplicantIdByUserIdForExistingShouldReturnApplicant()
        {
            await using var context = InMemoryContextProvider.GetDbContext();
            var repository = GetRepository(context);
            var user = userFaker.Generate();
            var applicant = applicantFaker.Generate();
            applicant.UserId = user.Id;
            await context.AddAsync(user);
            await repository.AddAsync(applicant, CancellationToken.None);
            await context.SaveChangesAsync();
            var result = await repository.GetApplicantIdByUserId(user.Id, CancellationToken.None);
            result.Should().Be(applicant.Id);
        }
        /// 
        /// Test for  method that should throw exception for not existing entity
        /// 
        [Fact]
        private async Task IsApplicantNonResidentByUserIdForNotExistingShouldThrow()
        {
            await using var context = InMemoryContextProvider.GetDbContext();
            var repository = GetRepository(context);
            ApplicantNotFoundByUserIdException? result = null;
            try
            {
                await repository.IsApplicantNonResidentByUserId(Guid.NewGuid(), CancellationToken.None);
            }
            catch (Exception e)
            {
                result = e as ApplicantNotFoundByUserIdException;
            }
            result.Should().NotBeNull();
        }
        /// 
        /// Test for  method that should return existing entity's IsNonResident property
        /// 
        [Fact]
        private async Task IsApplicantNonResidentByUserIdForExistingShouldReturnApplicant()
        {
            await using var context = InMemoryContextProvider.GetDbContext();
            var repository = GetRepository(context);
            var user = userFaker.Generate();
            var applicant = new ApplicantFaker(GetDateTimeProvider()).Generate();
            applicant.UserId = user.Id;
            await context.AddAsync(user);
            await repository.AddAsync(applicant, CancellationToken.None);
            await context.SaveChangesAsync();
            var result = await repository.IsApplicantNonResidentByUserId(user.Id, CancellationToken.None);
            result.Should().Be(applicant.IsNonResident);
        }
    }
}