using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.Security;
using Tiku.Domain.Identity;
using Tiku.Infrastructure.Auth;
using Tiku.Infrastructure.Bootstrap;
using Tiku.Infrastructure.Persistence;

namespace Tiku.UnitTests.Bootstrap;

public sealed class PlatformAdminBootstrapperTests
{
    private const string TemporaryPassword = "Temporary9Password";

    [Fact]
    public async Task Bootstrap_creates_forced_enrollment_super_admin_and_audit()
    {
        await using var provider = CreateProvider();
        await using var scope = provider.CreateAsyncScope();
        var bootstrapper = ActivatorUtilities.CreateInstance<PlatformAdminBootstrapper>(scope.ServiceProvider);

        var result = await bootstrapper.BootstrapAsync(new PlatformAdminBootstrapOptions(
            "admin@example.com",
            TemporaryPassword,
            "Initial Administrator"));

        var context = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        var user = await context.Users.SingleAsync(item => item.Id == result.UserId);
        Assert.True(user.ForcePasswordChange);
        Assert.True(user.EmailConfirmed);
        Assert.Equal(UserStatus.Active, user.Status);
        var role = await context.PlatformBackendRoles.SingleAsync(item => item.Id == result.RoleId);
        Assert.Equal(PlatformAdminBootstrapper.SuperAdminRoleCode, role.Code);
        Assert.True(role.IsSystem);
        Assert.Equal(BackendPermissions.Platform.Count, await context.PlatformBackendRolePermissions.CountAsync());
        Assert.True(
            await context.PlatformBackendUserRoles.AnyAsync(item => item.UserId == user.Id && item.RoleId == role.Id));
        Assert.True(await context.AuditLogs.AnyAsync(item =>
            item.ActorUserId == user.Id && item.Action == "platform.bootstrap_admin.created"));

        var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
        Assert.True(await userManager.CheckPasswordAsync(user, TemporaryPassword));
    }

    [Fact]
    public async Task Bootstrap_rejects_a_second_platform_administrator_without_mutating_data()
    {
        await using var provider = CreateProvider();
        await using var scope = provider.CreateAsyncScope();
        var bootstrapper = ActivatorUtilities.CreateInstance<PlatformAdminBootstrapper>(scope.ServiceProvider);
        await bootstrapper.BootstrapAsync(new PlatformAdminBootstrapOptions("first@example.com", TemporaryPassword));

        var exception = await Assert.ThrowsAsync<PlatformAdminBootstrapException>(() =>
            bootstrapper.BootstrapAsync(new PlatformAdminBootstrapOptions("second@example.com", TemporaryPassword)));

        Assert.Equal("platform_admin_already_exists", exception.Code);
        var context = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
        Assert.Single(context.PlatformBackendUserRoles);
        Assert.Single(context.PlatformBackendRoles);
        Assert.Single(context.AuditLogs);
        Assert.Single(context.Users);
    }

    [Fact]
    public async Task Bootstrap_rejects_an_existing_user_email()
    {
        await using var provider = CreateProvider();
        await using var scope = provider.CreateAsyncScope();
        var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
        var createResult = await userManager.CreateAsync(new User
        {
            Email = "existing@example.com",
            UserName = "existing@example.com"
        }, TemporaryPassword);
        Assert.True(createResult.Succeeded);
        var bootstrapper = ActivatorUtilities.CreateInstance<PlatformAdminBootstrapper>(scope.ServiceProvider);

        var exception = await Assert.ThrowsAsync<PlatformAdminBootstrapException>(() =>
            bootstrapper.BootstrapAsync(new PlatformAdminBootstrapOptions("existing@example.com", TemporaryPassword)));

        Assert.Equal("bootstrap_user_already_exists", exception.Code);
    }

    [Fact]
    public async Task Development_async_seed_creates_one_forced_enrollment_administrator()
    {
        await using var provider = CreateProvider();
        await using var scope = provider.CreateAsyncScope();
        var context = scope.ServiceProvider.GetRequiredService<TikuDbContext>();

        var created = await DevelopmentPlatformAdminSeeder.SeedAsync(context);
        var createdAgain = await DevelopmentPlatformAdminSeeder.SeedAsync(context);

        Assert.True(created);
        Assert.False(createdAgain);
        var user = await context.Users.SingleAsync();
        Assert.Equal(DevelopmentPlatformAdminSeeder.Email, user.Email);
        Assert.True(user.ForcePasswordChange);
        Assert.Equal(BackendPermissions.Platform.Count, await context.PlatformBackendRolePermissions.CountAsync());
        Assert.Single(context.PlatformBackendUserRoles);
        Assert.Single(context.AuditLogs);
        var quote = await context.PlatformBillingQuotes.SingleAsync();
        Assert.Equal(quote.OriginalAmountCents - quote.DiscountAmountCents, quote.TotalAmountCents);
        var order = await context.PlatformBillingOrders.SingleAsync();
        Assert.Equal(order.OriginalAmountCents - order.DiscountAmountCents, order.TotalAmountCents);
    }

    [Fact]
    public async Task Development_sync_seed_is_idempotent()
    {
        await using var provider = CreateProvider();
        await using var scope = provider.CreateAsyncScope();
        var context = scope.ServiceProvider.GetRequiredService<TikuDbContext>();

        var created = DevelopmentPlatformAdminSeeder.Seed(context);
        var createdAgain = DevelopmentPlatformAdminSeeder.Seed(context);

        Assert.True(created);
        Assert.False(createdAgain);
        Assert.Single(context.Users);
        Assert.Single(context.PlatformBackendUserRoles);
    }

    private static ServiceProvider CreateProvider()
    {
        var services = new ServiceCollection();
        services.AddLogging();
        services.AddDbContext<TikuDbContext>(options =>
            options.UseInMemoryDatabase(Guid.NewGuid().ToString()));
        services.AddIdentityCore<User>(options =>
            {
                options.Password.RequiredLength = 8;
                options.Password.RequireDigit = false;
                options.Password.RequireLowercase = false;
                options.Password.RequireUppercase = false;
                options.Password.RequireNonAlphanumeric = false;
            })
            .AddEntityFrameworkStores<TikuDbContext>()
            .AddDefaultTokenProviders()
            .AddPasswordValidator<LetterAndDigitPasswordValidator<User>>();
        services.AddDataProtection().UseEphemeralDataProtectionProvider();
        return services.BuildServiceProvider();
    }
}