using Microsoft.EntityFrameworkCore;
using Modules.Rating.Api.Database;
using Modules.Rating.Api.Database.Entities;
using System.Linq.Expressions;

namespace Modules.Rating.Api.Repositories;

public class RatingRepository(RatingDbContext context)
{
    private readonly RatingDbContext _context = context;
    public async Task AddAsync(Vote entity, CancellationToken cancellationToken)
    {
        if (await IsVoteExists(entity.ObjectId, entity.SubjectId)) throw new Exception("Object is already rated by subject");
        _context.Votes.Add(entity);
        await _context.SaveChangesAsync(cancellationToken);
    }

    public async Task<bool> UpdateAsync(Vote entity, CancellationToken cancellationToken)
    {
        var vote = await _context.Votes
            .FirstOrDefaultAsync(q => q.ObjectId == entity.ObjectId && q.SubjectId == entity.SubjectId, cancellationToken);
        if (vote == null) return false;
        vote.RatingPercentage = entity.RatingPercentage;
        await _context.SaveChangesAsync(cancellationToken);
        return true;
    }

    public async Task<bool> DeleteAsync(Guid objectId, Guid subjectId)
    {
        var rate = await _context.Votes.FirstOrDefaultAsync(q => q.ObjectId == objectId && q.SubjectId == subjectId);
        if (rate != null) _context.Votes?.Remove(rate);
        await _context.SaveChangesAsync();
        return rate != null;
    }

    public async Task<Vote?> GetFirstOrDefaultWhere(Expression<Func<Vote, bool>> predicate) =>
        await _context.Votes.Where(predicate).SingleOrDefaultAsync();

    public async Task<List<Vote>> GetWhere(Expression<Func<Vote, bool>> predicate) =>
        await _context.Votes.Where(predicate).ToListAsync();

    internal async Task<List<VoteItem>> GetVotes(IEnumerable<Guid> objectIds, Guid? subjectId)
    {
        var query = _context.Votes.AsQueryable();
        return await query
            .Where(q => objectIds.Contains(q.ObjectId))
            .GroupBy(q => q.ObjectId, (o, r) => new { ObjectId = o, Rate = r.Average(q => q.RatingPercentage) })
            .GroupJoin(query.Where(q => q.SubjectId == subjectId),
                q => q.ObjectId,
                q => q.ObjectId,
                (g, r) => new { g.ObjectId, g.Rate, SubjecrRates = r })
            .SelectMany(q => q.SubjecrRates.DefaultIfEmpty(), (r, s) =>
                new VoteItem { ObjectId = r.ObjectId, Rate = r.Rate, SubjectRate = s != null ? s.RatingPercentage : null })
            .ToListAsync().ConfigureAwait(false);

        //var builder = Builders<BsonDocument>.SetFields

        //await _collection
        //.Aggregate()
        //.Match(q => objectIds.Contains(q.Key.ObjectId))
        //.Group(q => q.Key.ObjectId, q => new { ObjectId = q.Key, Rate = q.Average(q => q.RatePercentage) })
        //.Lookup<Rate, RateItem>(nameof(Rate), q => q.ObjectId, q)
        //.FirstOrDefaultAsync().ConfigureAwait(false);
    }

    //internal async Task<List<RateItem>> GetRates(IEnumerable<Guid> objectIds, Guid? subjectId)
    //{
    //    var matchStage = new BsonDocument("$match", new BsonDocument("Key.ObjectId", new BsonDocument("$in", new BsonArray(objectIds))));

    //    var groupStage = new BsonDocument("$group", new BsonDocument
    //    {
    //        { "_id", "$Key.ObjectId" },
    //        { "AverageRate", new BsonDocument("$avg", "$RatePercentage") }
    //    });

    //    var lookupStage = new BsonDocument("$lookup", new BsonDocument
    //    {
    //        { "from", _collection.CollectionNamespace.CollectionName },
    //        { "let", new BsonDocument("objectId", "$_id") },
    //        { "pipeline", new BsonArray
    //            {
    //                new BsonDocument("$match", new BsonDocument("$expr", new BsonDocument("$and", new BsonArray
    //                {
    //                    new BsonDocument("$eq", new BsonArray { "$Key.ObjectId", "$$objectId" }),
    //                    //new BsonDocument("$eq", new BsonArray { "$Key.SubjectId", subjectId.ToString() ?? "" })
    //                    new BsonDocument("$eq", new BsonArray { "$Key.SubjectId", subjectId.ToString() ?? "" })
    //                })))
    //            }
    //        },
    //        { "as", "SubjectRates" }
    //    });

    //    var projectStage = new BsonDocument("$project", new BsonDocument
    //    {
    //        { "ObjectId", "$_id" },
    //        { "Rate", "$AverageRate" },
    //        { "SubjectRate", new BsonDocument("$arrayElemAt", new BsonArray { "$SubjectRates.RatePercentage", 0 }) }
    //    });

    //    var pipeline = new[] { matchStage, groupStage, lookupStage, projectStage };

    //    var result = await _collection.AggregateAsync<BsonDocument>(pipeline).ConfigureAwait(false);

    //    var rateItems = new List<RateItem>();

    //    //await result.ForEachAsync(doc =>
    //    //{
    //    //    rateItems.Add(new RateItem
    //    //    {
    //    //        ObjectId = doc["ObjectId"].AsGuid,
    //    //        Rate = doc["Rate"].AsDouble,
    //    //        SubjectRate = doc["SubjectRate"].IsBsonNull ? (ushort?)null : doc["SubjectRate"].AsNullableInt32
    //    //    });
    //    //}).ConfigureAwait(false);

    //    await result.ForEachAsync(doc =>
    //    {
    //        var subjectRate = doc.GetValue("SubjectRate", BsonNull.Value);
    //        rateItems.Add(new RateItem
    //        {
    //            ObjectId = doc.GetValue("ObjectId").AsGuid,
    //            Rate = doc.GetValue("Rate").AsDouble,
    //            SubjectRate = subjectRate.IsBsonNull ? null : subjectRate.AsNullableInt32
    //        });
    //    }).ConfigureAwait(false);

    //    return rateItems;

    //}

    internal class VoteItem
    {
        internal Guid ObjectId { get; set; } = default!;
        internal double Rate { get; set; }
        internal int? SubjectRate { get; set; }
    }
    
    public async Task<double?> GetAverageObjectRating(Guid objectId)
    {
        if (!await _context.Votes.AnyAsync(q => q.ObjectId == objectId)) return null;
        return await _context.Votes
        .Where(q => q.ObjectId == objectId)
        .GroupBy(q => q.ObjectId)
        .Select(q => q.Average(q => q.RatingPercentage))
        .FirstOrDefaultAsync().ConfigureAwait(false);
    }

    public async Task<bool> IsVoteExists(Guid objectId, Guid subjectId) =>
        await _context.Votes.Where(q => q.ObjectId == objectId && q.SubjectId == subjectId).AnyAsync();
}