#nullable enable
using Baya.Application.Models.Common;
using Baya.Application.Models.Reviews;
using Baya.Domain.Entities.Reviews;

namespace Baya.Application.Contracts.Persistence;

/// <summary>
/// The <c>reviews</c> aggregate (reviews + tag links). Writes load tracked rows; reads project to DTOs and
/// return <b>published</b> reviews only on any public path. The nurse rating aggregate is always derived from
/// source (<c>AVG</c>/<c>COUNT</c> over currently-published reviews) — never an incremental delta.
/// </summary>
public interface IReviewRepository
{
    Task AddAsync(Review review, CancellationToken cancellationToken);

    /// <summary>The booking's ownership + status + nurse, for the submit-eligibility guards. Null if absent.</summary>
    Task<ReviewableBooking?> GetReviewableBookingAsync(long bookingId, CancellationToken cancellationToken);

    /// <summary>True if a (non-deleted) review already exists for the booking — the 1:1 pre-check.</summary>
    Task<bool> ExistsForBookingAsync(long bookingId, CancellationToken cancellationToken);

    /// <summary>The caller's own review for a booking (with tags + moderation status) plus the owning
    /// customer's user id for tenancy. Null when the booking has no review.</summary>
    Task<MyReviewProjection?> GetMyReviewForBookingAsync(long bookingId, CancellationToken cancellationToken);

    /// <summary>Tracked review for a moderation transition. Null if absent.</summary>
    Task<Review?> GetTrackedAsync(long reviewId, CancellationToken cancellationToken);

    /// <summary>Tracked review with its tag links loaded — for the add/replace-tags command. Null if absent.</summary>
    Task<Review?> GetTrackedWithTagsAsync(long reviewId, CancellationToken cancellationToken);

    /// <summary>Validates tag codes against the active master vocabulary; returns the matched <c>code → id</c>
    /// (an unknown/inactive code is simply absent from the map, so the handler can reject it cleanly).</summary>
    Task<IReadOnlyDictionary<string, long>> GetTagIdsByCodesAsync(IReadOnlyList<string> codes, CancellationToken cancellationToken);

    /// <summary>
    /// The recompute-from-source stats for a nurse — <c>COUNT</c> and <c>SUM(rating)</c> over the nurse's
    /// currently <b>published</b> reviews <b>excluding</b> <paramref name="excludeReviewId"/>. The caller then
    /// folds in the transitioning review's <i>new</i> status in memory, so the aggregate is derived from source
    /// (not an incremental delta) and is correct <b>before</b> the single commit — a fresh query can't yet see
    /// the tracked, uncommitted status change. Pass <c>0</c> to exclude nothing (a brand-new review).
    /// </summary>
    Task<(int Count, long Sum)> GetPublishedRatingStatsExcludingAsync(long nurseProfileId, long excludeReviewId, CancellationToken cancellationToken);

    /// <summary>The nurse's denormalized, published-only rating aggregate (kept correct by the recompute) —
    /// what the public reviews read returns and caches.</summary>
    Task<NurseReviewAggregateDto> GetNurseAggregateAsync(long nurseProfileId, CancellationToken cancellationToken);

    /// <summary>Public, paginated list of a nurse's <b>published</b> reviews (with tag codes), newest first.</summary>
    Task<PagedResult<ReviewListItemDto>> ListPublishedForNurseAsync(long nurseProfileId, int page, int pageSize, CancellationToken cancellationToken);

    /// <summary>Admin moderation queue, filtered by status (default caller-supplied), newest first, with any
    /// linked low-rating alert id joined in.</summary>
    Task<PagedResult<ModerationQueueItemDto>> GetModerationQueueAsync(string? status, int page, int pageSize, CancellationToken cancellationToken);

    /// <summary>Per-nurse tag rollup over <b>published</b> reviews — each active tag's count and share.</summary>
    Task<NurseTagAggregatesResult> GetTagAggregatesAsync(long nurseProfileId, CancellationToken cancellationToken);
}
