using MongoDB.Driver;

namespace Guestlist;

public sealed class GuestEntryService(MongoDbContext db)
{
    private readonly IMongoCollection<GuestEntryDocument> _guestEntries =
        db.Database.GetCollection<GuestEntryDocument>("guest_entries");

    public async Task<List<GuestEntryDocument>> GetAllAsync()
    {
        return await _guestEntries
            .Find(FilterDefinition<GuestEntryDocument>.Empty)
            .SortBy(x => x.Name)
            .ToListAsync();
    }

    public async Task EnsureIndexesAsync()
    {
        var nameIndex = new CreateIndexModel<GuestEntryDocument>(
            Builders<GuestEntryDocument>.IndexKeys.Ascending(x => x.Name));

        var createdAtIndex = new CreateIndexModel<GuestEntryDocument>(
            Builders<GuestEntryDocument>.IndexKeys.Descending(x => x.CreatedAt));

        await _guestEntries.Indexes.CreateManyAsync([nameIndex, createdAtIndex]);
    }

    public async Task<bool> NameExistsAsync(string name, string? excludeId = null)
    {
        string normalizedName;

        try
        {
            normalizedName = InputRules.CleanRequiredText(name, "a guest name", InputRules.GuestNameMaxLength);
        }
        catch (InvalidOperationException)
        {
            return false;
        }

        var filter = Builders<GuestEntryDocument>.Filter.Regex(
            x => x.Name,
            new MongoDB.Bson.BsonRegularExpression($"^{System.Text.RegularExpressions.Regex.Escape(normalizedName)}$", "i"));

        if (!string.IsNullOrWhiteSpace(excludeId))
        {
            filter &= Builders<GuestEntryDocument>.Filter.Ne(x => x.Id, excludeId);
        }

        return await _guestEntries.CountDocumentsAsync(filter) > 0;
    }

    public async Task<bool> NameAndNotesExistAsync(string name, string? notes, string? excludeId = null)
    {
        string normalizedName;

        try
        {
            normalizedName = InputRules.CleanRequiredText(name, "a guest name", InputRules.GuestNameMaxLength);
        }
        catch (InvalidOperationException)
        {
            return false;
        }

        var normalizedNotes = InputRules.CleanOptionalText(notes, "Notes", InputRules.GuestNotesMaxLength);

        var filter = Builders<GuestEntryDocument>.Filter.Regex(
            x => x.Name,
            new MongoDB.Bson.BsonRegularExpression($"^{System.Text.RegularExpressions.Regex.Escape(normalizedName)}$", "i"));

        filter &= normalizedNotes is null
            ? Builders<GuestEntryDocument>.Filter.Or(
                Builders<GuestEntryDocument>.Filter.Eq(x => x.Notes, null),
                Builders<GuestEntryDocument>.Filter.Eq(x => x.Notes, ""))
            : Builders<GuestEntryDocument>.Filter.Regex(
                x => x.Notes,
                new MongoDB.Bson.BsonRegularExpression($"^{System.Text.RegularExpressions.Regex.Escape(normalizedNotes)}$", "i"));

        if (!string.IsNullOrWhiteSpace(excludeId))
        {
            filter &= Builders<GuestEntryDocument>.Filter.Ne(x => x.Id, excludeId);
        }

        return await _guestEntries.CountDocumentsAsync(filter) > 0;
    }

    public async Task AddAsync(string name, string? notes, string addedByUserId, string addedByUsername)
    {
        var cleanedName = InputRules.CleanRequiredText(name, "a guest name", InputRules.GuestNameMaxLength);
        var cleanedNotes = InputRules.CleanOptionalText(notes, "Notes", InputRules.GuestNotesMaxLength);

        var entry = new GuestEntryDocument
        {
            Name = cleanedName,
            Notes = cleanedNotes,
            AddedByUserId = addedByUserId,
            AddedByUsername = addedByUsername,
            CreatedAt = DateTime.UtcNow
        };

        await _guestEntries.InsertOneAsync(entry);
    }

    public async Task DeleteAsync(string id)
    {
        await _guestEntries.DeleteOneAsync(x => x.Id == id);
    }
    
    public async Task UpdateAsync(string id, string name, string? notes)
    {
        var cleanedName = InputRules.CleanRequiredText(name, "a guest name", InputRules.GuestNameMaxLength);
        var cleanedNotes = InputRules.CleanOptionalText(notes, "Notes", InputRules.GuestNotesMaxLength);

        var update = Builders<GuestEntryDocument>.Update
            .Set(x => x.Name, cleanedName)
            .Set(x => x.Notes, cleanedNotes);

        await _guestEntries.UpdateOneAsync(x => x.Id == id, update);
    }
}
