using Knot.Contracts.Profiles.Application.DTOs;
using Knot.Contracts.Profiles.Domain;
using Knot.Modules.Profiles.Infrastructure.Mappings;
using Knot.Shared.Kernel;
using MongoDB.Bson;
using MongoDB.Driver;

namespace Knot.Modules.Profiles.Infrastructure.Database;

internal class ProfileRepository : IProfileRepository
{
    private readonly IMongoCollection<ProfileDocument> _profiles;

    public ProfileRepository(IMongoDatabase database)
    {
        _profiles = database.GetCollection<ProfileDocument>("profiles");
    }

    public async Task<UserProfileDto?> GetAsync(Guid userId, CancellationToken ct = default)
    {
        var profile = await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(ct);
        return profile?.ToDto();
    }

    public async Task<UserProfileDto?> GetByUsernameAsync(string username, CancellationToken ct = default)
    {
        var profile = await _profiles.Find(p => p.Username == username).FirstOrDefaultAsync(ct);
        return profile?.ToDto();
    }

    public async Task<List<UserProfileDto>> GetAsync(IEnumerable<Guid> userIds, CancellationToken ct = default)
    {
        var ids = userIds.ToList();
        var profiles = await _profiles.Find(p => ids.Contains(p.Id)).ToListAsync(ct);
        return profiles.Select(p => p.ToDto()).ToList();
    }

    public async Task<List<UserProfileDto>> SearchAsync(string query, int limit = 20, CancellationToken ct = default)
    {
        var baseFilter = Builders<ProfileDocument>.Filter.And(
            Builders<ProfileDocument>.Filter.Ne(p => p.IsBanned, true),
            Builders<ProfileDocument>.Filter.Ne(p => p.IsDeleted, true)
        );

        List<ProfileDocument> docs;
        if (string.IsNullOrWhiteSpace(query))
        {
            docs = await _profiles.Find(baseFilter).Limit(limit).ToListAsync(ct);
        }
        else
        {
            var searchFilter = Builders<ProfileDocument>.Filter.Or(
                Builders<ProfileDocument>.Filter.Regex(p => p.Username, new BsonRegularExpression(query, "i")),
                Builders<ProfileDocument>.Filter.Regex(p => p.DisplayName, new BsonRegularExpression(query, "i"))
            );

            var combinedFilter = Builders<ProfileDocument>.Filter.And(baseFilter, searchFilter);
            docs = await _profiles.Find(combinedFilter).Limit(limit).ToListAsync(ct);
        }
        return docs.Select(p => p.ToDto()).ToList();
    }

    public async Task<Result<UserProfileDto>> CreateAsync(UserProfileDto dto, CancellationToken ct = default)
    {
        var profile = ProfileDocument.Create(dto.UserId, dto.Username ?? string.Empty, dto.DisplayName ?? string.Empty, dto.About);

        await _profiles.InsertOneAsync(profile, null, ct);
        return Result.Success(profile.ToDto());
    }

    public async Task<Result<UserProfileDto>> UpdateAsync(UserProfileDto dto, CancellationToken ct = default)
    {
        var document = await _profiles.Find(p => p.Id == dto.UserId).FirstOrDefaultAsync(ct);
        if (document is null)
            return Result.Failure<UserProfileDto>(ProfilesErrors.ProfileNotFound);

        document.UpdateProfile(
            dto.DisplayName ?? document.DisplayName,
            dto.About ?? document.Bio,
            dto.Birthday);

        document.UpdateAvatar(dto.Avatar);

        await _profiles.ReplaceOneAsync(p => p.Id == dto.UserId, document, cancellationToken: ct);
        return Result.Success(document.ToDto());
    }

    public async Task<Result> UpdateStatusAsync(Guid userId, bool isBanned, bool isDeleted, CancellationToken ct = default)
    {
        var profile = await _profiles.Find(p => p.Id == userId).FirstOrDefaultAsync(ct);
        if (profile is null) return Result.Failure(ProfilesErrors.ProfileNotFound);

        profile.UpdateStatus(isBanned, isDeleted);
        await _profiles.ReplaceOneAsync(p => p.Id == userId, profile, cancellationToken: ct);
        return Result.Success();
    }

    public async Task<Result> DeleteAsync(Guid userId, CancellationToken ct = default)
    {
        var result = await _profiles.DeleteOneAsync(p => p.Id == userId, ct);
        return result.DeletedCount > 0 ? Result.Success() : Result.Failure(ProfilesErrors.ProfileNotFound);
    }
}
