using System.Globalization;
using Baya.Application.Common;
using Baya.Application.Contracts.Common;
using Baya.Application.Features.Bookings.Commands.ConvertRequestToBooking;
using Baya.Domain.Entities.Booking;
using Microsoft.EntityFrameworkCore;
using NSubstitute;
using BookingEntity = Baya.Domain.Entities.Booking.Booking;

namespace Baya.Test.Foundation.Bookings;

public class BookingConversionTests
{
    private static readonly DateTimeOffset Now = new(2026, 7, 6, 10, 0, 0, TimeSpan.Zero);

    private static ConvertRequestToBookingCommandHandler Handler(BookingsTestHost host, IPaymentCaptureSimulator capture)
        => new(host.AsCustomer(), host.UnitOfWork, host.Config(), host.Clock(Now), capture, new VariantSnapshotSerializer(),
            Substitute.For<INotificationDispatcher>());

    [Fact]
    public async Task Convert_creates_confirmed_booking_with_reconciling_amounts_and_sessions()
    {
        using var host = new BookingsTestHost();
        var variantId = host.AddVariant(sessionCount: 3, price: 5_000_000); // gross = 15_000_000
        var requestId = host.AddAcceptedRequest(variantId);

        var result = await Handler(host, host.Capture()).Handle(new ConvertRequestToBookingCommand(requestId), CancellationToken.None);

        Assert.True(result.IsSuccess);
        var dto = result.Result;
        Assert.Equal(BookingStatus.Confirmed, dto.Status);

        var gross = long.Parse(dto.GrossPriceIrr, CultureInfo.InvariantCulture);
        var commission = long.Parse(dto.BalinyaarCommissionIrr, CultureInfo.InvariantCulture);
        var payout = long.Parse(dto.NursePayoutAmount, CultureInfo.InvariantCulture);
        Assert.Equal(15_000_000, gross);
        Assert.Equal(2_250_000, commission);     // 15M × 0.15
        Assert.Equal(gross, commission + payout);
        Assert.Equal(0.15m, dto.PlatformFeeRate);
        Assert.NotNull(dto.AddressSnapshotJson);
        Assert.Contains("خیابان اول", dto.AddressSnapshotJson!); // snapshot decrypted for the customer view

        Assert.Equal((short)3, dto.SessionCount);
        Assert.Equal(3, dto.Sessions.Count);
        var sessionPayoutSum = dto.Sessions.Sum(s => long.Parse(s.VisitPayoutAmount, CultureInfo.InvariantCulture));
        Assert.Equal(payout, sessionPayoutSum);

        // Request flipped to converted.
        var reqStatus = host.Db.Set<BookingRequest>().AsNoTracking().Single(r => r.Id == requestId).Status;
        Assert.Equal(BookingRequestStatus.Converted, reqStatus);
    }

    [Fact]
    public async Task Convert_single_visit_creates_exactly_one_session()
    {
        using var host = new BookingsTestHost();
        var variantId = host.AddVariant(sessionCount: null); // single visit
        var requestId = host.AddAcceptedRequest(variantId);

        var result = await Handler(host, host.Capture()).Handle(new ConvertRequestToBookingCommand(requestId), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.Equal((short)1, result.Result.SessionCount);
        Assert.Single(result.Result.Sessions);
    }

    [Fact]
    public async Task Convert_is_idempotent_returns_same_booking_on_replay()
    {
        using var host = new BookingsTestHost();
        var variantId = host.AddVariant(sessionCount: 2);
        var requestId = host.AddAcceptedRequest(variantId);

        var first = await Handler(host, host.Capture()).Handle(new ConvertRequestToBookingCommand(requestId), CancellationToken.None);
        var second = await Handler(host, host.Capture()).Handle(new ConvertRequestToBookingCommand(requestId), CancellationToken.None);

        Assert.True(first.IsSuccess);
        Assert.True(second.IsSuccess);
        Assert.Equal(first.Result.Id, second.Result.Id);
        Assert.Equal(1, host.Db.Set<BookingEntity>().Count());
    }

    [Fact]
    public async Task Convert_with_failed_capture_creates_no_booking_and_leaves_request_accepted()
    {
        using var host = new BookingsTestHost();
        var variantId = host.AddVariant(sessionCount: 1);
        var requestId = host.AddAcceptedRequest(variantId);

        var result = await Handler(host, host.Capture(succeeds: false)).Handle(new ConvertRequestToBookingCommand(requestId), CancellationToken.None);

        Assert.False(result.IsSuccess);
        Assert.Equal(0, host.Db.Set<BookingEntity>().Count());
        var reqStatus = host.Db.Set<BookingRequest>().AsNoTracking().Single(r => r.Id == requestId).Status;
        Assert.Equal(BookingRequestStatus.AcceptedAwaitingPayment, reqStatus);
    }
}
