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

namespace Baya.Application.Features.Reviews.Commands.ModerateReview;

/// <summary>
/// Applies a moderation transition and, in the <b>same transaction</b>: recomputes the nurse aggregate from
/// source (§ <see cref="RecomputeNurseRating"/>) and stages the b7 search-index refresh. The transition is
/// audited automatically because <see cref="Review"/> is <c>IAuditable</c> (the SaveChanges interceptor writes
/// the diff). After commit the cached aggregate is invalidated and the author is notified of the outcome. This
/// is the human decision authority — it can always override the AI pre-screen.
/// </summary>
internal sealed class ModerateReviewCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    ISearchIndexMaintainer searchIndex,
    ICacheService cache,
    IDateTimeProvider dateTimeProvider,
    INotificationDispatcher notifications)
    : IRequestHandler<ModerateReviewCommand, OperationResult<ModerateReviewResult>>
{
    public async ValueTask<OperationResult<ModerateReviewResult>> Handle(ModerateReviewCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } moderatorId)
            return OperationResult<ModerateReviewResult>.UnauthorizedResult("Not authenticated.");

        var targetStatus = ReviewModerationAction.ToStatus(request.Action);
        if (targetStatus is null)
            return OperationResult<ModerateReviewResult>.FailureResult($"Unknown moderation action '{request.Action}'.");

        var review = await unitOfWork.ReviewRepository.GetTrackedAsync(request.ReviewId, cancellationToken);
        if (review is null)
            return OperationResult<ModerateReviewResult>.NotFoundResult("Review not found.");

        var reason = ReviewModerationAction.RequiresReason(request.Action) ? request.Reason : null;
        review.Moderate(targetStatus, reason, moderatorId, dateTimeProvider.UtcNow);

        // From-source recompute + search refresh, staged on the same unit of work as the status change.
        await RecomputeNurseRating.ExecuteAsync(review, unitOfWork, searchIndex, cancellationToken);

        await unitOfWork.CommitAsync();

        await ReviewCache.InvalidateAggregateAsync(cache, review.NurseProfileId, cancellationToken);

        // Best-effort author notice of the outcome (in-app). Not on the critical path.
        var recipientUserId = await unitOfWork.CustomerProfileRepository.GetUserIdByProfileIdAsync(review.CustomerProfileId, cancellationToken);
        if (recipientUserId is { } uid)
            await notifications.DispatchAsync(
                new Notification(uid, "review_moderated", "Your review was updated",
                    $"Your review is now {review.ModerationStatus}.",
                    $"{{\"reviewId\":{review.Id},\"status\":\"{review.ModerationStatus}\"}}"),
                cancellationToken);

        var aggregate = await unitOfWork.ReviewRepository.GetNurseAggregateAsync(review.NurseProfileId, cancellationToken);
        return OperationResult<ModerateReviewResult>.SuccessResult(
            new ModerateReviewResult(review.Id, review.ModerationStatus, aggregate.AverageRating, aggregate.PublishedCount));
    }
}
