using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Mongo2Go;
using MongoDB.Driver;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard.Security;
using StellaOps.Authority.Plugin.Standard.Storage;

namespace StellaOps.Authority.Plugin.Standard.Tests;

public class StandardUserCredentialStoreTests : IAsyncLifetime
{
    private readonly MongoDbRunner runner;
    private readonly IMongoDatabase database;
    private readonly StandardPluginOptions options;
    private readonly StandardUserCredentialStore store;

    public StandardUserCredentialStoreTests()
    {
        runner = MongoDbRunner.Start(singleNodeReplSet: true);
        var client = new MongoClient(runner.ConnectionString);
        database = client.GetDatabase("authority-tests");
        options = new StandardPluginOptions
        {
            PasswordPolicy = new PasswordPolicyOptions
            {
                MinimumLength = 8,
                RequireDigit = true,
                RequireLowercase = true,
                RequireUppercase = true,
                RequireSymbol = false
            },
            Lockout = new LockoutOptions
            {
                Enabled = true,
                MaxAttempts = 2,
                WindowMinutes = 1
            }
        };
        store = new StandardUserCredentialStore(
            "standard",
            database,
            options,
            new Pbkdf2PasswordHasher(),
            NullLogger<StandardUserCredentialStore>.Instance);
    }

    [Fact]
    public async Task VerifyPasswordAsync_ReturnsSuccess_ForValidCredentials()
    {
        var registration = new AuthorityUserRegistration(
            "alice",
            "Password1!",
            "Alice",
            null,
            false,
            new[] { "admin" },
            new Dictionary<string, string?>());

        var upsert = await store.UpsertUserAsync(registration, CancellationToken.None);
        Assert.True(upsert.Succeeded);

        var result = await store.VerifyPasswordAsync("alice", "Password1!", CancellationToken.None);
        Assert.True(result.Succeeded);
        Assert.Equal("alice", result.User?.Username);
    }

    [Fact]
    public async Task VerifyPasswordAsync_EnforcesLockout_AfterRepeatedFailures()
    {
        await store.UpsertUserAsync(
            new AuthorityUserRegistration(
                "bob",
                "Password1!",
                "Bob",
                null,
                false,
                new[] { "operator" },
                new Dictionary<string, string?>()),
            CancellationToken.None);

        var first = await store.VerifyPasswordAsync("bob", "wrong", CancellationToken.None);
        Assert.False(first.Succeeded);
        Assert.Equal(AuthorityCredentialFailureCode.InvalidCredentials, first.FailureCode);

        var second = await store.VerifyPasswordAsync("bob", "stillwrong", CancellationToken.None);
        Assert.False(second.Succeeded);
        Assert.Equal(AuthorityCredentialFailureCode.LockedOut, second.FailureCode);
        Assert.NotNull(second.RetryAfter);
        Assert.True(second.RetryAfter.Value > System.TimeSpan.Zero);
    }

    public Task InitializeAsync() => Task.CompletedTask;

    public Task DisposeAsync()
    {
        runner.Dispose();
        return Task.CompletedTask;
    }
}
