#nullable enable
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Payments;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Models.Common;
using Baya.Application.Models.Payments;
using Baya.Domain.Entities.Booking;
using Baya.Domain.Entities.Payments;
using Mediator;

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

internal sealed class InitiatePaymentCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IPaymentProvider paymentProvider,
    IDateTimeProvider dateTimeProvider)
    : IRequestHandler<InitiatePaymentCommand, OperationResult<InitiatePaymentResult>>
{
    public async ValueTask<OperationResult<InitiatePaymentResult>> Handle(InitiatePaymentCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
            return OperationResult<InitiatePaymentResult>.UnauthorizedResult("Not authenticated.");

        var customerId = await unitOfWork.CustomerProfileRepository.GetProfileIdByUserIdAsync(userId, cancellationToken);
        if (customerId is null)
            return OperationResult<InitiatePaymentResult>.NotFoundResult("Booking request not found.");

        // Tenancy: only the owning customer may pay; any other caller must not learn the request exists.
        var ctx = await unitOfWork.BookingRequestRepository.GetPaymentContextAsync(request.BookingRequestId, cancellationToken);
        if (ctx is null || ctx.CustomerId != customerId)
            return OperationResult<InitiatePaymentResult>.NotFoundResult("Booking request not found.");

        // Already paid — do not open a second attempt. The filtered succeeded-unique is the structural backstop.
        if (await unitOfWork.PaymentRepository.HasSucceededTransactionForRequestAsync(request.BookingRequestId, cancellationToken))
            return OperationResult<InitiatePaymentResult>.ConflictResult("This booking has already been paid.");

        if (ctx.Status != BookingRequestStatus.AcceptedAwaitingPayment)
            return OperationResult<InitiatePaymentResult>.ConflictResult("This request is not awaiting payment.");

        var now = dateTimeProvider.UtcNow.UtcDateTime;
        if (ctx.PaymentDeadlineAt is { } deadline && deadline < now)
            return OperationResult<InitiatePaymentResult>.ConflictResult("The payment window for this request has lapsed.");

        // Config-driven selection: the active standard gateway with the lowest priority (swap a cut-off
        // provider by config, not a code change).
        var gatewayId = await unitOfWork.PaymentRepository.GetActiveGatewayIdAsync(PaymentGatewayType.Standard, cancellationToken);
        if (gatewayId is null)
            return OperationResult<InitiatePaymentResult>.FailureResult("No active payment gateway is configured.");

        var idempotencyKey = string.IsNullOrWhiteSpace(request.IdempotencyKey)
            ? $"br-{request.BookingRequestId}"
            : request.IdempotencyKey!;

        // amount is the request's frozen gross — never recomputed from client input.
        var init = await paymentProvider.InitPaymentAsync(request.BookingRequestId, ctx.GrossIrr, idempotencyKey, cancellationToken);

        // A retried initiate (same idempotency key ⇒ same reference) reuses the existing pending attempt
        // instead of colliding on the filtered UNIQUE(gateway_reference_code).
        var existing = await unitOfWork.PaymentRepository.GetTrackedTransactionByReferenceAsync(init.GatewayReferenceCode, cancellationToken);
        if (existing is not null)
            return OperationResult<InitiatePaymentResult>.SuccessResult(
                new InitiatePaymentResult(existing.Id, init.RedirectUrl, init.GatewayReferenceCode));

        var transaction = new PaymentTransaction
        {
            BookingRequestId = request.BookingRequestId,
            CustomerId = customerId.Value,
            GatewayId = gatewayId.Value,
            Amount = ctx.GrossIrr,
            Currency = "IRR",
            GatewayReferenceCode = init.GatewayReferenceCode,
            IpAddress = currentUser.IpAddress
        };

        await unitOfWork.PaymentRepository.AddTransactionAsync(transaction, cancellationToken);
        await unitOfWork.CommitAsync();

        return OperationResult<InitiatePaymentResult>.SuccessResult(
            new InitiatePaymentResult(transaction.Id, init.RedirectUrl, init.GatewayReferenceCode));
    }
}
