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

namespace Baya.Application.Contracts.Persistence;

/// <summary>
/// The <c>tickets</c> aggregate (tickets + participants + messages). Writes load tracked rows; reads project to
/// DTOs. The <b>hard visibility boundary</b> lives here: <see cref="GetMessagesAsync"/> only returns
/// <c>is_internal</c> messages when <paramref name="includeInternal"/> (the admin view) is true — an internal
/// note is filtered out of the projection for the user view, never surfaced downstream.
/// </summary>
public interface ITicketRepository
{
    Task AddAsync(Ticket ticket, CancellationToken cancellationToken);

    Task AddMessageAsync(TicketMessage message, CancellationToken cancellationToken);

    Task AddParticipantAsync(TicketParticipant participant, CancellationToken cancellationToken);

    /// <summary>Tracked ticket (no includes) — for close/reopen transitions. Null if absent.</summary>
    Task<Ticket?> GetTrackedAsync(long ticketId, CancellationToken cancellationToken);

    /// <summary>True if a ticket already uses this reference code — the collision check before minting.</summary>
    Task<bool> ReferenceCodeExistsAsync(string referenceCode, CancellationToken cancellationToken);

    /// <summary>Tracked participant row for a (ticket, user) — active or soft-removed — so an add can resurrect
    /// a removed row and a remove can soft-stamp it. Null if the user was never on the ticket.</summary>
    Task<TicketParticipant?> GetParticipantAsync(long ticketId, int userId, CancellationToken cancellationToken);

    /// <summary>True if the user is an <b>active</b> participant on the ticket — the authorization backstop for
    /// reads/posts.</summary>
    Task<bool> IsActiveParticipantAsync(long ticketId, int userId, CancellationToken cancellationToken);

    /// <summary>True if a <c>coordination</c> ticket already exists for the booking — the auto-create idempotency guard.</summary>
    Task<bool> CoordinationTicketExistsForBookingAsync(long bookingId, CancellationToken cancellationToken);

    /// <summary>The booking's customer + nurse <c>users.id</c> — the two participants of a coordination ticket.
    /// Null when the booking is absent.</summary>
    Task<BookingPartyUserIds?> GetBookingPartyUserIdsAsync(long bookingId, CancellationToken cancellationToken);

    /// <summary>The ticket's header facts (no messages) — for authorization + the thread response header. Null if absent.</summary>
    Task<TicketHeaderDto?> GetHeaderAsync(long ticketId, CancellationToken cancellationToken);

    /// <summary>True if the booking exists and the given user is its customer or nurse — the tenancy check for
    /// opening a booking-linked ticket.</summary>
    Task<bool> IsUserPartyToBookingAsync(long bookingId, int userId, CancellationToken cancellationToken);

    /// <summary>The active participants on a thread.</summary>
    Task<IReadOnlyList<TicketParticipantDto>> GetActiveParticipantsAsync(long ticketId, CancellationToken cancellationToken);

    /// <summary>The user ids of the active participants (for notification fan-out).</summary>
    Task<IReadOnlyList<int>> GetActiveParticipantUserIdsAsync(long ticketId, CancellationToken cancellationToken);

    /// <summary>The ordered thread messages. <paramref name="includeInternal"/> = false (the user view) strips
    /// every <c>is_internal</c> message in the projection; true (the admin view) returns them.</summary>
    Task<IReadOnlyList<TicketMessageDto>> GetMessagesAsync(long ticketId, bool includeInternal, CancellationToken cancellationToken);

    /// <summary>Paginated tickets the user participates in (active membership), filterable by status and
    /// reference code, newest first.</summary>
    Task<PagedResult<TicketSummaryDto>> ListMyTicketsAsync(int userId, string? status, string? referenceCode, long? bookingId, int page, int pageSize, CancellationToken cancellationToken);

    /// <summary>An existing message on the ticket carrying this client message id — the optimistic-send
    /// idempotency lookup. Null when none (a fresh send). Ignores internal notes.</summary>
    Task<TicketMessageDto?> GetMessageByClientIdAsync(long ticketId, string clientMessageId, CancellationToken cancellationToken);

    /// <summary>The admin global queue — paginated, filter by status/category, search by reference code, optional
    /// booking/refund link, newest first.</summary>
    Task<PagedResult<TicketSummaryDto>> ListForAdminAsync(string? status, string? category, string? referenceCode, long? bookingId, long? refundId, int page, int pageSize, CancellationToken cancellationToken);
}

/// <summary>The customer + nurse owning <c>users.id</c> for a booking.</summary>
public readonly record struct BookingPartyUserIds(int CustomerUserId, int NurseUserId);
