using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Tiku.Application.Auth;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Auth;
using Tiku.Infrastructure.Persistence;

namespace Tiku.IntegrationTests.Api;

public sealed class SmsVerificationConcurrencyTests
{
    [Fact]
    public async Task Concurrent_verification_consumes_a_code_exactly_once()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedCodeAsync(factory, "123456");

        var results = await Task.WhenAll(
            TryVerifyAsync(factory, seed.TenantId, "123456"),
            TryVerifyAsync(factory, seed.TenantId, "123456"));

        Assert.Single(results, succeeded => succeeded);
        using var scope = factory.CreateSystemScope("Verify concurrent SMS consumption");
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        var verification = await dbContext.SmsVerificationCodes.SingleAsync(item => item.Id == seed.CodeId);
        Assert.Equal(SmsVerificationStatus.Verified, verification.Status);
        Assert.NotNull(verification.ConsumedAt);
    }

    [Fact]
    public async Task Concurrent_invalid_attempts_atomically_block_on_the_fifth_failure()
    {
        await using var factory = new ApiTestFactory();
        var seed = await SeedCodeAsync(factory, "123456");

        var results = await Task.WhenAll(Enumerable.Range(0, 5)
            .Select(_ => TryVerifyAsync(factory, seed.TenantId, "999999")));

        Assert.DoesNotContain(true, results);
        using var scope = factory.CreateSystemScope("Verify concurrent SMS blocking");
        var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        var verification = await dbContext.SmsVerificationCodes.SingleAsync(item => item.Id == seed.CodeId);
        Assert.Equal(5, verification.Attempts);
        Assert.Equal(SmsVerificationStatus.Blocked, verification.Status);
    }

    private static async Task<bool> TryVerifyAsync(ApiTestFactory factory, Guid tenantId, string code)
    {
        using var scope = factory.CreateSystemScope("Concurrent SMS verification");
        var service = scope.ServiceProvider.GetRequiredService<ISmsVerificationService>();
        try
        {
            await service.VerifyCodeAsync(tenantId, "13800000000", SmsPurpose.Login, code);
            return true;
        }
        catch (InvalidCredentialsException)
        {
            return false;
        }
    }

    private static async Task<(Guid TenantId, Guid CodeId)> SeedCodeAsync(
        ApiTestFactory factory,
        string code)
    {
        using var scope = factory.CreateSystemScope("Read SMS test options");
        var options = scope.ServiceProvider.GetRequiredService<IOptions<SmsSecurityOptions>>().Value;
        var tenantId = Guid.NewGuid();
        var verification = new SmsVerificationCode
        {
            TenantId = tenantId,
            Phone = "13800000000",
            Purpose = SmsPurpose.Login,
            CodeHash = SmsCodeHashing.Hash(
                tenantId,
                "13800000000",
                SmsPurpose.Login,
                code,
                options.CodePepper),
            Status = SmsVerificationStatus.Sent,
            ExpiresAt = DateTimeOffset.UtcNow.AddMinutes(5)
        };
        await factory.SeedAsync(
            new Tenant
            {
                Id = tenantId,
                Slug = tenantId.ToString("N"),
                Name = "Concurrent SMS Tenant"
            },
            verification);
        return (tenantId, verification.Id);
    }
}