using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Knot.Contracts.Messaging.Abstractions;
using Knot.Contracts.Messaging.Application.Abstractions;
using Knot.Contracts.Messaging.Domain;
using Knot.Modules.Messaging.Application.Abstractions;
using MongoDB.Bson;
using MongoDB.Driver;
using ContractUserStatsService = Knot.Contracts.Messaging.Application.Abstractions.IUserStatsService;
using ModuleUserStatsService = Knot.Modules.Messaging.Application.Abstractions.IUserStatsService;

namespace Knot.Modules.Messaging.Infrastructure.Persistence.Mongo;

public sealed class UserStatsService : ModuleUserStatsService, ContractUserStatsService
{
    private readonly IMongoCollection<Message> _messages;

    public UserStatsService(IMongoDatabase database)
    {
        _messages = database.GetCollection<Message>("messages");
    }

    public async Task<Dictionary<Guid, Knot.Modules.Messaging.Application.Abstractions.UserStats>> GetStatsForUsersAsync(IEnumerable<Guid> userIds, CancellationToken ct = default)
    {
        if (!userIds.Any()) return new Dictionary<Guid, Knot.Modules.Messaging.Application.Abstractions.UserStats>();

        var stats = await _messages.Aggregate()
            .Match(Builders<Message>.Filter.In(m => m.SenderId, userIds))
            .Group(new BsonDocument {
                { "_id", "$SenderId" },
                { "Count", new BsonDocument("$sum", 1) },
                { "MediaSize", new BsonDocument("$sum", new BsonDocument("$sum", "$Media.Size")) }
            })
            .ToListAsync(ct);

        return stats.ToDictionary(
            doc => doc["_id"].AsGuid,
            doc => new Knot.Modules.Messaging.Application.Abstractions.UserStats(
                doc["Count"].AsInt32,
                doc.Contains("MediaSize") && !doc["MediaSize"].IsBsonNull ? (long)(doc["MediaSize"].IsInt64 ? doc["MediaSize"].AsInt64 : doc["MediaSize"].AsInt32) : 0L
            )
        );
    }

    async Task<Dictionary<Guid, Knot.Contracts.Messaging.Application.Abstractions.UserStats>> Knot.Contracts.Messaging.Application.Abstractions.IUserStatsService.GetStatsForUsersAsync(List<Guid> userIds, CancellationToken cancellationToken = default)
    {
        if (!userIds.Any()) return new Dictionary<Guid, Knot.Contracts.Messaging.Application.Abstractions.UserStats>();

        var stats = await _messages.Aggregate()
            .Match(Builders<Message>.Filter.In(m => m.SenderId, userIds))
            .Group(new BsonDocument {
                { "_id", "$SenderId" },
                { "Count", new BsonDocument("$sum", 1) },
                { "MediaSize", new BsonDocument("$sum", new BsonDocument("$sum", "$Media.Size")) }
            })
            .ToListAsync(cancellationToken);

        return stats.ToDictionary(
            doc => doc["_id"].AsGuid,
            doc => new Knot.Contracts.Messaging.Application.Abstractions.UserStats(
                doc["Count"].AsInt32,
                doc.Contains("MediaSize") && !doc["MediaSize"].IsBsonNull ? (long)(doc["MediaSize"].IsInt64 ? doc["MediaSize"].AsInt64 : doc["MediaSize"].AsInt32) : 0L
            )
        );
    }

    public async Task<long> GetTotalStorageSizeAsync(CancellationToken ct = default)
    {
        var result = await _messages.Aggregate()
            .Group(new BsonDocument {
                { "_id", BsonNull.Value },
                { "TotalSize", new BsonDocument("$sum", new BsonDocument("$sum", "$Media.Size")) }
            })
            .FirstOrDefaultAsync(ct);

        if (result == null) return 0;
        return result.Contains("TotalSize") ? (long)(result["TotalSize"].IsInt64 ? result["TotalSize"].AsInt64 : result["TotalSize"].AsInt32) : 0L;
    }

    public async Task<int> GetCountOrphanedMessagesAsync(HashSet<Guid> activeChatIds, CancellationToken ct = default)
    {
        var filter = Builders<Message>.Filter.Or(
            Builders<Message>.Filter.BitsAnySet(m => m.State, (long)MessageState.IsDeleted),
            Builders<Message>.Filter.Nin(m => m.ChatId, activeChatIds)
        );
        return (int)(await _messages.CountDocumentsAsync(filter, cancellationToken: ct));
    }

    public async Task<long> GetOrphanedMediaSizeAsync(HashSet<string> validFileIds, CancellationToken ct = default)
    {
        return 0;
    }
}
