#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 Baya.Domain.Entities.Messaging;
using Mediator;

namespace Baya.Application.Features.Messaging.Commands.OpenTicket;

/// <summary>
/// Creates the ticket + the opener participant (+ an optional first message) in one commit. Enforces the link
/// tenancy: a booking link requires the opener to be a party to the booking (staff bypass — staff read/attach to
/// any ticket); a refund link is staff-only (the b11 dispute paper trail). The <c>reference_code</c> is minted
/// once, collision-checked, and backed by the UNIQUE index.
/// </summary>
internal sealed class OpenTicketCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IDateTimeProvider dateTimeProvider)
    : IRequestHandler<OpenTicketCommand, OperationResult<OpenTicketResult>>
{
    public async ValueTask<OperationResult<OpenTicketResult>> Handle(OpenTicketCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
            return OperationResult<OpenTicketResult>.UnauthorizedResult("Not authenticated.");

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

        if (request.BookingId is { } bookingId && !isStaff)
        {
            // Tenancy: a non-staff opener must be the booking's customer or nurse. A mismatch is a clean 404.
            var isParty = await unitOfWork.TicketRepository.IsUserPartyToBookingAsync(bookingId, userId, cancellationToken);
            if (!isParty)
                return OperationResult<OpenTicketResult>.NotFoundResult("Booking not found.");
        }

        if (request.RefundId is not null && !isStaff)
            return OperationResult<OpenTicketResult>.ForbiddenResult("Only staff can anchor a ticket to a refund.");

        var now = dateTimeProvider.UtcNow;
        var referenceCode = await TicketReferenceCode.MintAsync(unitOfWork.TicketRepository, cancellationToken);

        var ticket = new Ticket
        {
            ReferenceCode = referenceCode,
            Subject = request.Subject,
            Category = request.Category,
            BookingId = request.BookingId,
            RefundId = request.RefundId,
            OpenedById = userId
        };
        ticket.Participants.Add(new TicketParticipant
        {
            UserId = userId,
            RoleOnTicket = TicketRoleResolver.Derive(currentUser.Roles),
            AddedById = userId
        });

        if (!string.IsNullOrWhiteSpace(request.Body))
            ticket.Messages.Add(new TicketMessage
            {
                SenderId = userId,
                Body = request.Body,
                IsInternal = false,
                SentAt = now
            });

        await unitOfWork.TicketRepository.AddAsync(ticket, cancellationToken);
        await unitOfWork.CommitAsync();

        return OperationResult<OpenTicketResult>.SuccessResult(
            new OpenTicketResult(ticket.Id, ticket.ReferenceCode, ticket.Status, ticket.Category));
    }
}
