using Microsoft.EntityFrameworkCore;
using ChatService.Domain.AggregatesModel.UserAggregate;
using ChatService.Domain.SeedWork;

namespace ChatService.Infrastructure.Repositories;

/// <summary>
/// EN: Repository implementation for ChatUser aggregate.
/// VI: Repository implementation cho ChatUser aggregate.
/// </summary>
public class ChatUserRepository : IChatUserRepository
{
    private readonly ChatServiceContext _context;

    public IUnitOfWork UnitOfWork => _context;

    public ChatUserRepository(ChatServiceContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public async Task<ChatUser?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await _context.ChatUsers
            .Include(u => u.Status)
            .FirstOrDefaultAsync(u => u.Id == id, cancellationToken);
    }

    public async Task<ChatUser?> GetByIdentityUserIdAsync(string identityUserId, CancellationToken cancellationToken = default)
    {
        return await _context.ChatUsers
            .Include(u => u.Status)
            .FirstOrDefaultAsync(u => u.IdentityUserId == identityUserId, cancellationToken);
    }

    public async Task<ChatUser?> GetWithKeysAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await _context.ChatUsers
            .Include(u => u.Status)
            .Include(u => u.OneTimePreKeys.Where(k => !k.IsUsed))
            .FirstOrDefaultAsync(u => u.Id == id, cancellationToken);
    }

    public async Task<IEnumerable<ChatUser>> GetByIdsAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken = default)
    {
        return await _context.ChatUsers
            .Include(u => u.Status)
            .Where(u => ids.Contains(u.Id))
            .ToListAsync(cancellationToken);
    }

    public async Task<(string? IdentityPublicKey, string? SignedPreKey, string? SignedPreKeySignature, OneTimePreKey? OneTimePreKey)> 
        GetKeyBundleAsync(Guid userId, CancellationToken cancellationToken = default)
    {
        var user = await _context.ChatUsers
            .Include(u => u.OneTimePreKeys.Where(k => !k.IsUsed).Take(1))
            .FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);

        if (user?.KeyBundle == null)
            return (null, null, null, null);

        var oneTimePreKey = user.OneTimePreKeys.FirstOrDefault();
        if (oneTimePreKey != null)
        {
            oneTimePreKey.MarkAsUsed();
        }

        return (
            user.KeyBundle.IdentityPublicKey,
            user.KeyBundle.SignedPreKey,
            user.KeyBundle.SignedPreKeySignature,
            oneTimePreKey
        );
    }

    public async Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await _context.ChatUsers.AnyAsync(u => u.Id == id, cancellationToken);
    }

    public async Task<bool> ExistsByIdentityUserIdAsync(string identityUserId, CancellationToken cancellationToken = default)
    {
        return await _context.ChatUsers.AnyAsync(u => u.IdentityUserId == identityUserId, cancellationToken);
    }

    public async Task<int> GetAvailablePreKeyCountAsync(Guid userId, CancellationToken cancellationToken = default)
    {
        return await _context.OneTimePreKeys
            .Where(k => k.UserId == userId && !k.IsUsed)
            .CountAsync(cancellationToken);
    }

    public ChatUser Add(ChatUser user)
    {
        return _context.ChatUsers.Add(user).Entity;
    }

    public void Update(ChatUser user)
    {
        _context.Entry(user).State = EntityState.Modified;
    }
}
