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

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

/// <summary>
/// Replaces a review's tag links with exactly the requested set. Authorized for the review's author or a
/// moderator; a non-owner non-moderator is denied. Tag codes are validated against the active master
/// vocabulary; the resulting set is de-duplicated so the <c>UNIQUE(review_id, review_tag_master_id)</c> is
/// never violated.
/// </summary>
internal sealed class AttachReviewTagsCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork)
    : IRequestHandler<AttachReviewTagsCommand, OperationResult<ReviewTagsResult>>
{
    private static readonly string[] ModeratorRoles = [RoleNames.Admin, RoleNames.SuperAdmin, RoleNames.Moderation];

    public async ValueTask<OperationResult<ReviewTagsResult>> Handle(AttachReviewTagsCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
            return OperationResult<ReviewTagsResult>.UnauthorizedResult("Not authenticated.");

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

        var isModerator = currentUser.Roles.Any(ModeratorRoles.Contains);
        if (!isModerator)
        {
            var customerProfileId = await unitOfWork.CustomerProfileRepository.GetProfileIdByUserIdAsync(userId, cancellationToken);
            if (customerProfileId != review.CustomerProfileId)
                return OperationResult<ReviewTagsResult>.ForbiddenResult("You can only tag your own review.");
        }

        var distinct = request.TagCodes.Select(c => c.Trim()).Where(c => c.Length > 0).Distinct().ToList();
        var resolved = await unitOfWork.ReviewRepository.GetTagIdsByCodesAsync(distinct, cancellationToken);
        var unknown = distinct.Where(c => !resolved.ContainsKey(c)).ToList();
        if (unknown.Count > 0)
            return OperationResult<ReviewTagsResult>.FailureResult($"Unknown review tag(s): {string.Join(", ", unknown)}.");

        // Replace: EF deletes the removed links and inserts the new ones in one transaction.
        review.TagLinks.Clear();
        foreach (var tagId in resolved.Values)
            review.TagLinks.Add(new ReviewTagLink { ReviewId = review.Id, ReviewTagMasterId = tagId });

        await unitOfWork.CommitAsync();

        return OperationResult<ReviewTagsResult>.SuccessResult(new ReviewTagsResult(review.Id, distinct));
    }
}
