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

namespace ChatService.Infrastructure.Repositories;

/// <summary>
/// EN: Repository implementation for Conversation aggregate.
/// VI: Repository implementation cho Conversation aggregate.
/// </summary>
public class ConversationRepository : IConversationRepository
{
    private readonly ChatServiceContext _context;

    public IUnitOfWork UnitOfWork => _context;

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

    public async Task<Conversation?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await _context.Conversations
            .Include(c => c.Type)
            .Include(c => c.Participants.Where(p => p.LeftAt == null))
            .FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
    }

    public async Task<Conversation?> GetWithMessagesAsync(Guid id, int messageCount = 50, CancellationToken cancellationToken = default)
    {
        var conversation = await _context.Conversations
            .Include(c => c.Type)
            .Include(c => c.Participants.Where(p => p.LeftAt == null))
            .FirstOrDefaultAsync(c => c.Id == id, cancellationToken);

        if (conversation == null)
            return null;

        // EN: Load messages separately for pagination
        // VI: Load messages riêng để phân trang
        var messages = await _context.Messages
            .Include(m => m.Type)
            .Include(m => m.Status)
            .Where(m => m.ConversationId == id)
            .OrderByDescending(m => m.CreatedAt)
            .Take(messageCount)
            .ToListAsync(cancellationToken);

        return conversation;
    }

    public async Task<IEnumerable<Conversation>> GetUserConversationsAsync(
        Guid userId, 
        int skip = 0, 
        int take = 20, 
        CancellationToken cancellationToken = default)
    {
        return await _context.Conversations
            .Include(c => c.Type)
            .Include(c => c.Participants.Where(p => p.LeftAt == null))
            .Where(c => c.Participants.Any(p => p.UserId == userId && p.LeftAt == null))
            .OrderByDescending(c => c.LastMessageAt ?? c.CreatedAt)
            .Skip(skip)
            .Take(take)
            .ToListAsync(cancellationToken);
    }

    public async Task<Conversation?> FindDirectConversationAsync(Guid user1Id, Guid user2Id, CancellationToken cancellationToken = default)
    {
        // EN: Find direct conversation between two users
        // VI: Tìm conversation trực tiếp giữa hai users
        return await _context.Conversations
            .Include(c => c.Type)
            .Include(c => c.Participants)
            .Where(c => c.Type == ConversationType.Direct)
            .Where(c => c.Participants.Any(p => p.UserId == user1Id && p.LeftAt == null))
            .Where(c => c.Participants.Any(p => p.UserId == user2Id && p.LeftAt == null))
            .FirstOrDefaultAsync(cancellationToken);
    }

    public async Task<IEnumerable<Message>> GetMessagesAsync(
        Guid conversationId,
        int skip = 0,
        int take = 50,
        DateTime? before = null,
        CancellationToken cancellationToken = default)
    {
        var query = _context.Messages
            .Include(m => m.Type)
            .Include(m => m.Status)
            .Where(m => m.ConversationId == conversationId);

        if (before.HasValue)
        {
            query = query.Where(m => m.CreatedAt < before.Value);
        }

        return await query
            .OrderByDescending(m => m.CreatedAt)
            .Skip(skip)
            .Take(take)
            .ToListAsync(cancellationToken);
    }

    public async Task<int> GetUnreadCountAsync(Guid conversationId, Guid userId, CancellationToken cancellationToken = default)
    {
        var participant = await _context.ConversationParticipants
            .FirstOrDefaultAsync(p => p.ConversationId == conversationId && p.UserId == userId, cancellationToken);

        if (participant == null)
            return 0;

        var lastReadAt = participant.LastReadAt ?? participant.JoinedAt;

        return await _context.Messages
            .Where(m => m.ConversationId == conversationId)
            .Where(m => m.SenderId != userId)
            .Where(m => m.CreatedAt > lastReadAt)
            .CountAsync(cancellationToken);
    }

    public async Task<int> GetTotalUnreadCountAsync(Guid userId, CancellationToken cancellationToken = default)
    {
        var userConversations = await _context.ConversationParticipants
            .Where(p => p.UserId == userId && p.LeftAt == null)
            .ToListAsync(cancellationToken);

        var totalUnread = 0;

        foreach (var participant in userConversations)
        {
            var lastReadAt = participant.LastReadAt ?? participant.JoinedAt;
            var unreadCount = await _context.Messages
                .Where(m => m.ConversationId == participant.ConversationId)
                .Where(m => m.SenderId != userId)
                .Where(m => m.CreatedAt > lastReadAt)
                .CountAsync(cancellationToken);
            
            totalUnread += unreadCount;
        }

        return totalUnread;
    }

    public Conversation Add(Conversation conversation)
    {
        return _context.Conversations.Add(conversation).Entity;
    }

    public void Update(Conversation conversation)
    {
        _context.Entry(conversation).State = EntityState.Modified;
    }

    public async Task<(IEnumerable<Conversation> Conversations, int TotalCount)> GetConversationsForUserAsync(
        Guid userId,
        int page = 1,
        int pageSize = 20,
        CancellationToken cancellationToken = default)
    {
        var query = _context.Conversations
            .Include(c => c.Type)
            .Include(c => c.Participants.Where(p => p.LeftAt == null))
            .Where(c => c.Participants.Any(p => p.UserId == userId && p.LeftAt == null));

        var totalCount = await query.CountAsync(cancellationToken);

        var conversations = await query
            .OrderByDescending(c => c.LastMessageAt ?? c.CreatedAt)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync(cancellationToken);

        return (conversations, totalCount);
    }

    public async Task<(IEnumerable<Message> Messages, int TotalCount)> GetMessagesPaginatedAsync(
        Guid conversationId,
        int page = 1,
        int pageSize = 50,
        DateTime? before = null,
        CancellationToken cancellationToken = default)
    {
        var query = _context.Messages
            .Include(m => m.Type)
            .Include(m => m.Status)
            .Where(m => m.ConversationId == conversationId);

        if (before.HasValue)
        {
            query = query.Where(m => m.CreatedAt < before.Value);
        }

        var totalCount = await query.CountAsync(cancellationToken);

        var messages = await query
            .OrderByDescending(m => m.CreatedAt)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync(cancellationToken);

        return (messages, totalCount);
    }

    public async Task<int> GetUnreadMessageCountAsync(Guid conversationId, Guid userId, CancellationToken cancellationToken = default)
    {
        return await GetUnreadCountAsync(conversationId, userId, cancellationToken);
    }

    public async Task<bool> IsUserParticipantAsync(Guid conversationId, Guid userId, CancellationToken cancellationToken = default)
    {
        return await _context.ConversationParticipants
            .AnyAsync(p => p.ConversationId == conversationId && p.UserId == userId && p.LeftAt == null, cancellationToken);
    }

    public async Task<Conversation?> GetByIdWithMessagesAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await _context.Conversations
            .Include(c => c.Type)
            .Include(c => c.Participants.Where(p => p.LeftAt == null))
            .Include(c => c.Messages)
                .ThenInclude(m => m.Type)
            .Include(c => c.Messages)
                .ThenInclude(m => m.Status)
            .FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
    }
}

