#nullable enable
using Baya.Application.Common;
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Models.Common;
using Baya.Application.Models.Messaging;
using Mediator;

namespace Baya.Application.Features.Messaging.Queries.GetTicketThread;

/// <summary>
/// Loads the header, active participants, and messages of a ticket the caller may read. Internal messages are
/// included only in the admin view (staff) — the user view's projection strips them. Authorization: the admin
/// view requires staff; the user view requires an active participant (staff may also read the user view).
/// </summary>
internal sealed class GetTicketThreadQueryHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IDateTimeProvider dateTimeProvider)
    : IRequestHandler<GetTicketThreadQuery, OperationResult<TicketThreadDto>>
{
    public async ValueTask<OperationResult<TicketThreadDto>> Handle(GetTicketThreadQuery request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
            return OperationResult<TicketThreadDto>.UnauthorizedResult("Not authenticated.");

        var isStaff = StaffRoles.IsStaff(currentUser.Roles);

        if (request.AsAdmin && !isStaff)
            return OperationResult<TicketThreadDto>.ForbiddenResult("Admin ticket view requires staff access.");

        var header = await unitOfWork.TicketRepository.GetHeaderAsync(request.TicketId, cancellationToken);
        if (header is null)
            return OperationResult<TicketThreadDto>.NotFoundResult("Ticket not found.");

        if (!request.AsAdmin && !isStaff &&
            !await unitOfWork.TicketRepository.IsActiveParticipantAsync(request.TicketId, userId, cancellationToken))
            return OperationResult<TicketThreadDto>.ForbiddenResult("You are not a participant on this ticket.");

        // Internal notes surface ONLY in the admin view; the user view never receives them.
        var includeInternal = request.AsAdmin && isStaff;

        var participants = await unitOfWork.TicketRepository.GetActiveParticipantsAsync(request.TicketId, cancellationToken);
        var messages = await unitOfWork.TicketRepository.GetMessagesAsync(request.TicketId, includeInternal, cancellationToken);

        // Fetching the user-facing thread marks it read for the caller (drives the inbox unread count) — admins
        // reading the staff view do not consume a participant's read state.
        if (!request.AsAdmin)
        {
            var participant = await unitOfWork.TicketRepository.GetParticipantAsync(request.TicketId, userId, cancellationToken);
            if (participant is { IsActive: true })
            {
                participant.MarkRead(dateTimeProvider.UtcNow);
                await unitOfWork.CommitAsync();
            }
        }

        return OperationResult<TicketThreadDto>.SuccessResult(new TicketThreadDto(
            header.Id, header.ReferenceCode, header.Subject, header.Status, header.Category,
            header.BookingId, header.RefundId, header.OpenedById, header.ClosedAt, participants, messages));
    }
}
