#nullable enable
using System;
using System.Text.Json;
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Configuration;
using Baya.Application.Contracts.Payments;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Features.Bookings;
using Baya.Application.Models.Common;
using Baya.Domain.Entities.Payments;
using Mediator;
using Microsoft.EntityFrameworkCore;

namespace Baya.Application.Features.Payments.Commands.ConfirmPaymentAndPostLedger;

internal sealed class ConfirmPaymentAndPostLedgerCommandHandler(
    IUnitOfWork unitOfWork,
    IPlatformConfig platformConfig,
    IDateTimeProvider dateTimeProvider,
    IPaymentProvider paymentProvider,
    ISettlementSplitProvider settlementSplitProvider,
    IVariantSnapshotSerializer variantSnapshotSerializer,
    INotificationDispatcher notifications,
    ISender sender)
    : IRequestHandler<ConfirmPaymentAndPostLedgerCommand, OperationResult<bool>>
{
    public async ValueTask<OperationResult<bool>> Handle(ConfirmPaymentAndPostLedgerCommand request, CancellationToken cancellationToken)
    {
        var transaction = await unitOfWork.PaymentRepository.GetTrackedTransactionByIdAsync(request.PaymentTransactionId, cancellationToken);
        if (transaction is null)
            return OperationResult<bool>.NotFoundResult("Payment transaction not found.");

        // Already captured — idempotent no-op success (a replayed confirm must not re-post).
        if (transaction.Status == PaymentTransactionStatus.Succeeded)
            return OperationResult<bool>.SuccessResult(true);

        var now = dateTimeProvider.UtcNow.UtcDateTime;

        // Never trust a callback alone — re-verify amount + reference with the gateway before confirming.
        var verify = await paymentProvider.VerifyAsync(transaction.GatewayReferenceCode ?? string.Empty, transaction.Amount, cancellationToken);
        if (verify.Status != PaymentProviderStatus.Succeeded || verify.AmountIrr != transaction.Amount)
        {
            transaction.MarkFailed(verify.Status.ToString(), null);
            await unitOfWork.CommitAsync();
            return OperationResult<bool>.FailureResult("The payment could not be verified with the gateway.");
        }

        // Create/confirm the booking through the shared conversion (idempotent on UNIQUE booking_request_id).
        var conversion = await BookingConversion.EnsureBookingAsync(
            unitOfWork, platformConfig, variantSnapshotSerializer, transaction.BookingRequestId, now, cancellationToken);
        if (conversion is null)
            return OperationResult<bool>.ConflictResult("This request can no longer be converted to a booking.");

        var booking = conversion.BookingId;
        transaction.MarkSucceeded(booking, verify.Status.ToString(), transaction.GatewayResponseJson);

        // Idempotent ledger: don't post a second capture group if one already exists for this transaction.
        if (!await unitOfWork.PaymentRepository.LedgerGroupExistsForTransactionAsync(transaction.Id, cancellationToken))
        {
            var legs = LedgerPosting.CardCapture(
                booking, conversion.NurseId, conversion.GrossIrr, conversion.CommissionIrr, conversion.PayoutIrr, transaction.Id, now);
            await unitOfWork.PaymentRepository.AddLedgerEntriesAsync(legs, cancellationToken);
        }

        try
        {
            await unitOfWork.CommitAsync();
        }
        catch (DbUpdateException)
        {
            // A concurrent confirm for a different transaction of the same booking hit the filtered
            // UNIQUE(booking_id) WHERE status='succeeded' — the DB backstop. Treat as already-captured.
            await unitOfWork.RollBackAsync();
            return OperationResult<bool>.SuccessResult(true);
        }

        // The lawful تسهیم split to the registered IBANs (the provider credits each directly; the platform
        // never moves money). The mock records the intent; a real adapter resolves each beneficiary's SHEBA.
        await settlementSplitProvider.RegisterSplitAsync(
            booking,
            [
                new SettlementLeg("nurse-registered-sheba", conversion.PayoutIrr, "nurse"),
                new SettlementLeg("platform-registered-sheba", conversion.CommissionIrr, "platform")
            ],
            cancellationToken);

        if (conversion.Created && conversion.CustomerUserId is { } customerUserId && conversion.NurseUserId is { } nurseUserId)
            await NotifyConfirmedAsync(customerUserId, nurseUserId, booking, cancellationToken);

        // Open the booking-coordination ticket (nurse + customer) once the booking is confirmed. Idempotent —
        // one coordination ticket per booking; a replayed confirm is a no-op (b15).
        if (conversion.Created)
        {
            await sender.Send(new Messaging.Commands.AutoCreateCoordinationTicket.AutoCreateCoordinationTicketCommand(booking), cancellationToken);

            // Auto-issue the commission invoice so the customer can reach «دانلود فاکتور» right after capture
            // (was admin-only). Idempotent per booking — a replayed confirm returns the existing invoice.
            await sender.Send(new Invoices.Commands.IssueInvoice.IssueInvoiceCommand(booking), cancellationToken);
        }

        return OperationResult<bool>.SuccessResult(true);
    }

    private async Task NotifyConfirmedAsync(int customerUserId, int nurseUserId, long bookingId, CancellationToken cancellationToken)
    {
        var payload = JsonSerializer.Serialize(new { booking_id = bookingId });

        await notifications.DispatchAsync(
            new Notification(customerUserId, "booking_confirmed", "Booking confirmed",
                "Your payment was captured and your booking is confirmed.", payload),
            cancellationToken);

        await notifications.DispatchAsync(
            new Notification(nurseUserId, "booking_confirmed_nurse", "New confirmed booking",
                "A booking has been confirmed and paid. The care instructions and schedule are now available.", payload),
            cancellationToken);
    }
}
