#nullable enable
using Baya.Domain.Common;
using Baya.Domain.Entities.Catalog;
using Baya.Domain.Entities.Identity;

namespace Baya.Domain.Entities.Booking;

/// <summary>
/// A customer's <b>pre-payment intent</b> for a specific nurse, patient, service variant, address, date and
/// time. It is deliberately money-free and separate from <c>bookings</c> (b9): a request can be rejected,
/// time out, or have its payment window lapse without a booking ever existing. The nurse sees only the
/// limited, unencrypted <see cref="CustomerNotes"/> before accepting — stage 1 of the two-stage clinical
/// disclosure boundary (the full encrypted care instructions are b9's, post-confirmation).
/// <para>
/// Both deadlines are <b>computed once from config and frozen</b> on the row, so a later config change can
/// never move an existing request's deadlines. Status changes only through the forward-only
/// <see cref="BookingRequestTransitions"/> guard.
/// </para>
/// </summary>
public class BookingRequest : BaseEntity<long>
{
    public long CustomerId { get; set; }
    public CustomerProfile Customer { get; set; } = null!;

    public long NurseId { get; set; }
    public NurseProfile Nurse { get; set; } = null!;

    public long PatientId { get; set; }
    public Patient Patient { get; set; } = null!;

    public long VariantId { get; set; }
    public NurseServiceVariant Variant { get; set; } = null!;

    public long CustomerAddressId { get; set; }
    public CustomerAddress CustomerAddress { get; set; } = null!;

    /// <summary>Closed code set — see <see cref="CaregiverGender"/>. Matched against the nurse's gender at
    /// request time; a first-class filter, never a soft preference.</summary>
    public string? RequiredCaregiverGender { get; set; }

    public DateOnly RequestedDate { get; set; }
    public TimeOnly RequestedTimeStart { get; set; }
    public TimeOnly RequestedTimeEnd { get; set; }

    /// <summary><b>Unencrypted, request-stage only</b> — the ONLY clinical context the nurse sees before
    /// accepting (Principle 6, stage 1). Never routed through the field encryptor; deliberately limited.</summary>
    public string? CustomerNotes { get; set; }

    /// <summary>Guarded — mutated only through the transition methods so every write goes through the
    /// forward-only status machine.</summary>
    public string Status { get; private set; } = BookingRequestStatus.PendingNurseResponse;

    /// <summary>UTC <c>datetime2</c>, frozen at create time (<c>now + nurse_response_deadline_hours</c>),
    /// immune to later config changes. After it passes an unanswered request auto-expires. Stored as
    /// <see cref="DateTime"/> (not <see cref="DateTimeOffset"/>) because it is compared/sorted in queries and
    /// the SQLite test provider cannot translate <c>DateTimeOffset</c> operators.</summary>
    public DateTime NurseResponseDeadlineAt { get; set; }

    /// <summary>Null until accept; then frozen to <c>now + booking_payment_deadline_minutes</c> (= 30). UTC.</summary>
    public DateTime? PaymentDeadlineAt { get; private set; }

    public string? NurseRejectionReason { get; private set; }

    public DateTimeOffset? DeletedAt { get; set; }

    public bool CanTransitionTo(string target) => BookingRequestTransitions.CanTransition(Status, target);

    /// <summary>Nurse accepts — opens the (already-computed) 30-minute payment window. No booking, no money.</summary>
    public void Accept(DateTime paymentDeadlineAt)
    {
        Transition(BookingRequestStatus.AcceptedAwaitingPayment);
        PaymentDeadlineAt = paymentDeadlineAt;
    }

    public void Reject(string reason)
    {
        Transition(BookingRequestStatus.RejectedByNurse);
        NurseRejectionReason = reason;
    }

    public void CancelByCustomer() => Transition(BookingRequestStatus.CancelledByCustomer);

    public void ExpireNoResponse() => Transition(BookingRequestStatus.ExpiredNoResponse);

    public void ExpirePaymentWindow() => Transition(BookingRequestStatus.PaymentDeadlineExpired);

    /// <summary>Marks the request converted — called only by b9 when it creates the <c>bookings</c> row.</summary>
    public void MarkConverted() => Transition(BookingRequestStatus.Converted);

    // Callers pre-check with CanTransitionTo and return a clean 409; reaching an illegal edge here is a
    // programming error, so it fails fast rather than silently overwriting a terminal state.
    private void Transition(string target)
    {
        if (!BookingRequestTransitions.CanTransition(Status, target))
            throw new InvalidOperationException($"Illegal booking-request transition {Status} → {target}.");

        Status = target;
    }
}
