using Microsoft.AspNetCore.Identity;
using MongoDB.Driver;

namespace Guestlist;

public sealed class AccessCodeService(MongoDbContext db)
{
    private const string GuestAccessCodeId = "guest-access";

    private readonly IMongoCollection<AccessCodeDocument> _accessCodes =
        db.Database.GetCollection<AccessCodeDocument>("access_codes");

    private readonly PasswordHasher<AccessCodeDocument> _hasher = new();

    public async Task EnsureIndexesAsync()
    {
        var activeIndex = new CreateIndexModel<AccessCodeDocument>(
            Builders<AccessCodeDocument>.IndexKeys.Ascending(x => x.IsActive));

        await _accessCodes.Indexes.CreateOneAsync(activeIndex);
    }

    public async Task SetGuestAccessCodeAsync(string code, string label = "Guest access")
    {
        var cleanedCode = InputRules.CleanAccessCode(code);
        var cleanedLabel = InputRules.CleanRequiredText(label, "a label", 80);

        var document = new AccessCodeDocument
        {
            Id = GuestAccessCodeId,
            Label = cleanedLabel,
            IsActive = true,
            CreatedAt = DateTime.UtcNow
        };

        document.CodeHash = _hasher.HashPassword(document, cleanedCode);

        await _accessCodes.ReplaceOneAsync(
            x => x.Id == GuestAccessCodeId,
            document,
            new ReplaceOptions { IsUpsert = true });
    }

    public async Task<bool> HasGuestAccessCodeAsync()
    {
        return await _accessCodes.CountDocumentsAsync(x => x.Id == GuestAccessCodeId && x.IsActive) > 0;
    }

    public async Task<bool> ValidateGuestAccessCodeAsync(string code)
    {
        string cleanedCode;

        try
        {
            cleanedCode = InputRules.CleanAccessCode(code);
        }
        catch (InvalidOperationException)
        {
            return false;
        }

        var document = await _accessCodes
            .Find(x => x.Id == GuestAccessCodeId && x.IsActive)
            .FirstOrDefaultAsync();

        if (document is null)
        {
            return false;
        }

        if (document.ExpiresAt is not null && document.ExpiresAt <= DateTime.UtcNow)
        {
            return false;
        }

        var result = _hasher.VerifyHashedPassword(
            document,
            document.CodeHash,
            cleanedCode);

        return result == PasswordVerificationResult.Success;
    }
}
