#nullable enable
using Baya.Application.Contracts.Persistence;
using Baya.Application.Contracts.Search;
using Baya.Domain.Entities.Reviews;

namespace Baya.Application.Features.Reviews;

/// <summary>
/// Recomputes a nurse's denormalized rating aggregate <b>from source</b> — never by an incremental
/// <c>+delta</c>/<c>-delta</c>. It reads <c>COUNT</c>/<c>SUM(rating)</c> over the nurse's currently
/// <b>published</b> reviews (excluding the transitioning review), then folds in that review's <i>new</i>
/// moderation status. This is the fix for inflated-rating-after-hide drift: hiding a 1-star lowers the count
/// and re-derives the average from what remains public.
/// <para>
/// It mutates the tracked <see cref="Baya.Domain.Entities.Identity.NurseProfile"/> and <b>stages</b> the b7
/// search-index refresh on the same unit of work; the caller's single <c>CommitAsync</c> persists the review
/// transition, the aggregate, and the projection atomically. It does not commit. Invoked by <b>every</b>
/// moderation transition and by an auto-published/auto-hidden submit.
/// </para>
/// </summary>
internal static class RecomputeNurseRating
{
    public static async Task ExecuteAsync(
        Review changedReview,
        IUnitOfWork unitOfWork,
        ISearchIndexMaintainer searchIndex,
        CancellationToken cancellationToken)
    {
        var profile = await unitOfWork.NurseProfileRepository.GetTrackedByIdAsync(changedReview.NurseProfileId, cancellationToken);
        if (profile is null)
            return;

        var (count, sum) = await unitOfWork.ReviewRepository
            .GetPublishedRatingStatsExcludingAsync(changedReview.NurseProfileId, changedReview.Id, cancellationToken);

        if (changedReview.ModerationStatus == ReviewModerationStatus.Published)
        {
            count += 1;
            sum += changedReview.Rating;
        }

        var average = count > 0 ? Math.Round(sum / (decimal)count, 2, MidpointRounding.AwayFromZero) : 0m;
        profile.SetReviewAggregates(average, count);

        // Any aggregate change must reach the search projection in the same transaction (staged inline).
        await searchIndex.ReindexNurseAsync(profile, null, cancellationToken);
    }
}
