using Baya.Application.Features.Payments.Commands.ConfirmPaymentAndPostLedger;
using Baya.Application.Features.Payments.Commands.InitiatePayment;
using Baya.Domain.Entities.Booking;
using Baya.Domain.Entities.Payments;
using Microsoft.EntityFrameworkCore;
using BookingEntity = Baya.Domain.Entities.Booking.Booking;

namespace Baya.Test.Foundation.Payments;

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

    // §7 worked example: gross 23_300_000, commission 3_495_000 (×0.15), payout 19_805_000.
    private const long Price = 23_300_000;

    private static InitiatePaymentCommandHandler Initiate(PaymentsTestHost host)
        => new(host.AsCustomer(), host.UnitOfWork, host.PaymentProvider, host.Clock(Now));

    private static ConfirmPaymentAndPostLedgerCommandHandler Confirm(PaymentsTestHost host)
        => new(host.UnitOfWork, host.Config(), host.Clock(Now), host.PaymentProvider, host.Settlement, host.Serializer, host.Notifications(), TestSenders.WithTicketHooks());

    private static async Task<long> InitiatePendingAsync(PaymentsTestHost host, long requestId)
    {
        var result = await Initiate(host).Handle(new InitiatePaymentCommand(requestId, null), CancellationToken.None);
        Assert.True(result.IsSuccess);
        return result.Result.TransactionId;
    }

    [Fact]
    public async Task Confirm_posts_balanced_card_capture_group_and_confirms_booking()
    {
        using var host = new PaymentsTestHost();
        var variantId = host.AddVariant(sessionCount: 1, price: Price);
        var requestId = host.AddAcceptedRequest(variantId);
        var txnId = await InitiatePendingAsync(host, requestId);

        var result = await Confirm(host).Handle(new ConfirmPaymentAndPostLedgerCommand(txnId), CancellationToken.None);
        Assert.True(result.IsSuccess);

        // Transaction captured + bound to a booking.
        var txn = host.Db.Set<PaymentTransaction>().AsNoTracking().Single(t => t.Id == txnId);
        Assert.Equal(PaymentTransactionStatus.Succeeded, txn.Status);
        Assert.NotNull(txn.BookingId);

        // Booking created + confirmed.
        var booking = host.Db.Set<BookingEntity>().AsNoTracking().Single();
        Assert.Equal(BookingStatus.Confirmed, booking.Status);

        // Exactly one balanced card-capture group with the three correct legs.
        var legs = host.Db.Set<LedgerEntry>().AsNoTracking()
            .Where(l => l.SourceRefType == LedgerSourceRefType.PaymentTransaction && l.SourceRefId == txnId)
            .ToList();
        Assert.Equal(3, legs.Count);
        Assert.Single(legs.Select(l => l.TransactionGroupId).Distinct());

        var debits = legs.Where(l => l.Direction == LedgerDirection.Debit).Sum(l => l.AmountIrr);
        var credits = legs.Where(l => l.Direction == LedgerDirection.Credit).Sum(l => l.AmountIrr);
        Assert.Equal(debits, credits);
        Assert.Equal(Price, legs.Single(l => l.AccountType == LedgerAccountType.EscrowHeld).AmountIrr);
        Assert.Equal(3_495_000, legs.Single(l => l.AccountType == LedgerAccountType.PlatformRevenue).AmountIrr);

        var payable = legs.Single(l => l.AccountType == LedgerAccountType.NursePayable);
        Assert.Equal(19_805_000, payable.AmountIrr);
        Assert.Equal(host.NurseId, payable.NurseId);
    }

    [Fact]
    public async Task Confirm_is_idempotent_no_second_ledger_group()
    {
        using var host = new PaymentsTestHost();
        var variantId = host.AddVariant(sessionCount: 1, price: Price);
        var requestId = host.AddAcceptedRequest(variantId);
        var txnId = await InitiatePendingAsync(host, requestId);

        await Confirm(host).Handle(new ConfirmPaymentAndPostLedgerCommand(txnId), CancellationToken.None);
        var second = await Confirm(host).Handle(new ConfirmPaymentAndPostLedgerCommand(txnId), CancellationToken.None);

        Assert.True(second.IsSuccess);
        Assert.Equal(3, host.Db.Set<LedgerEntry>().AsNoTracking().Count());
        Assert.Equal(1, host.Db.Set<BookingEntity>().AsNoTracking().Count());
    }

    [Fact]
    public async Task Second_succeeded_transaction_for_a_booking_is_blocked_by_the_filtered_unique()
    {
        using var host = new PaymentsTestHost();
        var variantId = host.AddVariant(sessionCount: 1, price: Price);
        var requestId = host.AddAcceptedRequest(variantId);

        var firstTxnId = await InitiatePendingAsync(host, requestId);
        await Confirm(host).Handle(new ConfirmPaymentAndPostLedgerCommand(firstTxnId), CancellationToken.None);

        // A second, distinct pending attempt for the same (now-captured) booking.
        var secondTxn = new PaymentTransaction
        {
            BookingRequestId = requestId, CustomerId = host.CustomerId, GatewayId = host.GatewayId,
            Amount = Price, Currency = "IRR", GatewayReferenceCode = "second-attempt-ref"
        };
        host.Db.Set<PaymentTransaction>().Add(secondTxn);
        host.Db.SaveChanges();

        // Confirming it must not create a second capture — the filtered UNIQUE(booking_id) WHERE succeeded
        // backstops it into an idempotent no-op success.
        var result = await Confirm(host).Handle(new ConfirmPaymentAndPostLedgerCommand(secondTxn.Id), CancellationToken.None);
        Assert.True(result.IsSuccess);

        // Still exactly one capture group; the second attempt was rolled back (not succeeded).
        Assert.Equal(3, host.Db.Set<LedgerEntry>().AsNoTracking().Count());
        var reloaded = host.Db.Set<PaymentTransaction>().AsNoTracking().Single(t => t.Id == secondTxn.Id);
        Assert.NotEqual(PaymentTransactionStatus.Succeeded, reloaded.Status);
    }
}
