#nullable enable
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
using Baya.Application.Contracts.Common;
using Baya.Application.Models.Booking;
using Baya.Domain.Entities.Booking;
using BookingEntity = Baya.Domain.Entities.Booking.Booking;

namespace Baya.Application.Features.Bookings;

/// <summary>
/// The single place the confirmed <c>bookings</c> row (+ its reconciling sessions + the two frozen snapshots)
/// is built from an <c>accepted_awaiting_payment</c> request. It holds the <b>conversion/amount logic</b> so
/// both the b9 <c>ConvertRequestToBooking</c> path (mock-capture trigger) and the b10 real card-capture
/// confirm path share it rather than duplicate it. Pure construction only — no DB, no commit, no capture, no
/// current-user gate; the caller owns tenancy, capture, idempotency and persistence.
/// </summary>
internal static class BookingFactory
{
    /// <summary>session_count comes from the variant (a single visit is 1).</summary>
    public static int SessionCount(BookingConversionSource source)
        => source.VariantSnapshot.SessionCount is { } sc && sc > 0 ? sc : 1;

    /// <summary>The charged gross (IRR) = variant price × session count. Frozen onto the booking at build time.</summary>
    public static long Gross(BookingConversionSource source)
        => source.VariantSnapshot.Price * SessionCount(source);

    /// <summary>
    /// Builds the booking (status <c>confirmed</c>) with its ≥ 1 reconciling sessions and the two frozen
    /// snapshots. The three amounts derive from the snapshotted <paramref name="rate"/> via
    /// <see cref="BookingAmounts"/> so <c>gross = commission + payout</c> always holds.
    /// </summary>
    public static BookingEntity Create(
        BookingConversionSource source,
        decimal rate,
        DateTime now,
        long? pspFeeAmount,
        IVariantSnapshotSerializer variantSnapshotSerializer)
    {
        var sessionCount = SessionCount(source);
        var gross = source.VariantSnapshot.Price * sessionCount;
        var (commission, payout) = BookingAmounts.Split(gross, rate);

        var booking = new BookingEntity
        {
            BookingRequestId = source.RequestId,
            CustomerId = source.CustomerId,
            NurseId = source.NurseId,
            PatientId = source.PatientId,
            VariantId = source.VariantId,
            CustomerAddressId = source.CustomerAddressId,
            VariantSnapshotJson = variantSnapshotSerializer.Serialize(source.VariantSnapshot),
            AddressSnapshotJson = SerializeAddress(source.AddressSnapshot),
            GrossPriceIrr = gross,
            BalinyaarCommissionIrr = commission,
            PlatformFeeRate = rate,
            NursePayoutAmount = payout,
            PspFeeAmount = pspFeeAmount,
            SessionCount = (short)sessionCount,
            ScheduledDate = source.RequestedDate,
            ScheduledTimeStart = source.RequestedTimeStart,
            ScheduledTimeEnd = source.RequestedTimeEnd
        };

        // Always ≥ 1 session; Σ(visit_payout_amount) reconciles exactly to nurse_payout_amount.
        var visitPayouts = BookingAmounts.SplitPayout(payout, sessionCount);
        for (var i = 0; i < sessionCount; i++)
        {
            booking.Sessions.Add(new BookingSession
            {
                SessionIndex = i + 1,
                ScheduledDate = source.RequestedDate,
                ScheduledTimeStart = source.RequestedTimeStart,
                ScheduledTimeEnd = source.RequestedTimeEnd,
                VisitPayoutAmount = visitPayouts[i]
            });
        }

        booking.TransitionTo(BookingStatus.Confirmed, now);
        return booking;
    }

    // Persian labels land in the snapshot as readable text (not \uXXXX escapes), mirroring the variant
    // snapshot serializer; the encoder still escapes HTML-sensitive ASCII so the stored JSON stays safe.
    private static readonly JsonSerializerOptions AddressJson = new()
    {
        Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
    };

    public static string SerializeAddress(AddressSnapshot a) => JsonSerializer.Serialize(new
    {
        addressId = a.AddressId,
        title = a.Title,
        cityId = a.CityId,
        cityNameFa = a.CityNameFa,
        cityNameEn = a.CityNameEn,
        districtId = a.DistrictId,
        districtNameFa = a.DistrictNameFa,
        districtNameEn = a.DistrictNameEn,
        addressLine = a.AddressLine,
        postalCode = a.PostalCode,
        recipientName = a.RecipientName,
        recipientPhone = a.RecipientPhone,
        latitude = a.Latitude,
        longitude = a.Longitude
    }, AddressJson);
}
