// EN: Unit tests for ProcessPaymentCallbackCommandHandler - payment callback (IPN) processing.
// VI: Unit tests cho ProcessPaymentCallbackCommandHandler - xu ly callback (IPN) thanh toan.

using FluentAssertions;
using Moq;
using WalletService.API.Application.Commands.Payments;
using WalletService.Domain.AggregatesModel.PaymentAggregate;
using WalletService.Domain.Exceptions;
using WalletService.Domain.SeedWork;
using Microsoft.Extensions.Logging;
using Xunit;

namespace WalletService.UnitTests.Application.Commands;

/// <summary>
/// EN: Unit tests for ProcessPaymentCallbackCommandHandler.
/// VI: Unit tests cho ProcessPaymentCallbackCommandHandler.
/// </summary>
public class ProcessPaymentCallbackCommandHandlerTests
{
    private readonly Mock<IPaymentRepository> _paymentRepositoryMock;
    private readonly Mock<IPaymentGateway> _vnpayGatewayMock;
    private readonly Mock<IUnitOfWork> _unitOfWorkMock;
    private readonly Mock<ILogger<ProcessPaymentCallbackCommandHandler>> _loggerMock;
    private readonly ProcessPaymentCallbackCommandHandler _handler;

    public ProcessPaymentCallbackCommandHandlerTests()
    {
        _paymentRepositoryMock = new Mock<IPaymentRepository>();
        _vnpayGatewayMock = new Mock<IPaymentGateway>();
        _unitOfWorkMock = new Mock<IUnitOfWork>();
        _loggerMock = new Mock<ILogger<ProcessPaymentCallbackCommandHandler>>();

        _vnpayGatewayMock.Setup(g => g.GatewayName).Returns("VNPay");

        _unitOfWorkMock.Setup(u => u.SaveEntitiesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);
        _paymentRepositoryMock.Setup(r => r.UnitOfWork).Returns(_unitOfWorkMock.Object);

        var gateways = new List<IPaymentGateway> { _vnpayGatewayMock.Object };

        _handler = new ProcessPaymentCallbackCommandHandler(
            _paymentRepositoryMock.Object,
            gateways,
            _loggerMock.Object);
    }

    /// <summary>
    /// EN: Helper to create a Payment entity in Processing state for callback testing.
    /// VI: Helper de tao Payment entity o trang thai Processing de test callback.
    /// </summary>
    private static Payment CreateProcessingPayment(Guid orderId)
    {
        var payment = new Payment(orderId, 100_000m, "VND", "VNPay");
        payment.MarkAsProcessing("https://vnpay.vn/pay", "TXN_ORIGINAL");
        return payment;
    }

    [Fact]
    public async Task Handle_WithValidCallback_ShouldUpdatePaymentStatusToCompleted()
    {
        // Arrange
        var orderId = Guid.NewGuid();
        var payment = CreateProcessingPayment(orderId);

        _vnpayGatewayMock.Setup(g => g.ValidateCallback(It.IsAny<IDictionary<string, string>>(), It.IsAny<string>()))
            .Returns(true);
        _paymentRepositoryMock.Setup(r => r.GetByOrderIdAsync(orderId))
            .ReturnsAsync(payment);

        var parameters = new Dictionary<string, string>
        {
            { "vnp_SecureHash", "valid_hash_123" },
            { "vnp_TxnRef", orderId.ToString() },
            { "vnp_ResponseCode", "00" },
            { "vnp_TransactionNo", "VNP_TXN_001" }
        };

        var command = new ProcessPaymentCallbackCommand("VNPay", parameters);

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Success.Should().BeTrue();
        result.PaymentId.Should().Be(payment.Id);
        result.OrderId.Should().Be(orderId);
        result.Status.Should().Be("Completed");
        result.ErrorMessage.Should().BeNull();

        _paymentRepositoryMock.Verify(r => r.Update(payment), Times.Once);
        _unitOfWorkMock.Verify(u => u.SaveEntitiesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task Handle_WithInvalidSignature_ShouldRejectCallback()
    {
        // Arrange
        _vnpayGatewayMock.Setup(g => g.ValidateCallback(It.IsAny<IDictionary<string, string>>(), It.IsAny<string>()))
            .Returns(false);

        var parameters = new Dictionary<string, string>
        {
            { "vnp_SecureHash", "invalid_hash" },
            { "vnp_TxnRef", Guid.NewGuid().ToString() },
            { "vnp_ResponseCode", "00" }
        };

        var command = new ProcessPaymentCallbackCommand("VNPay", parameters);

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        // EN: Invalid signature should be rejected without updating any payment.
        // VI: Chu ky khong hop le nen bi tu choi ma khong cap nhat payment nao.
        result.Should().NotBeNull();
        result.Success.Should().BeFalse();
        result.Status.Should().Be("InvalidSignature");
        result.ErrorMessage.Should().Contain("Invalid callback signature");

        _paymentRepositoryMock.Verify(r => r.GetByOrderIdAsync(It.IsAny<Guid>()), Times.Never);
        _paymentRepositoryMock.Verify(r => r.Update(It.IsAny<Payment>()), Times.Never);
    }

    [Fact]
    public async Task Handle_WithNonExistentPayment_ShouldReturnNotFound()
    {
        // Arrange
        var orderId = Guid.NewGuid();

        _vnpayGatewayMock.Setup(g => g.ValidateCallback(It.IsAny<IDictionary<string, string>>(), It.IsAny<string>()))
            .Returns(true);
        _paymentRepositoryMock.Setup(r => r.GetByOrderIdAsync(orderId))
            .ReturnsAsync((Payment?)null);

        var parameters = new Dictionary<string, string>
        {
            { "vnp_SecureHash", "valid_hash" },
            { "vnp_TxnRef", orderId.ToString() },
            { "vnp_ResponseCode", "00" },
            { "vnp_TransactionNo", "VNP_TXN_002" }
        };

        var command = new ProcessPaymentCallbackCommand("VNPay", parameters);

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Success.Should().BeFalse();
        result.PaymentId.Should().BeNull();
        result.OrderId.Should().Be(orderId);
        result.Status.Should().Be("NotFound");
        result.ErrorMessage.Should().Contain("Payment not found");
    }

    [Fact]
    public async Task Handle_WithFailedResponseCode_ShouldUpdatePaymentToFailed()
    {
        // Arrange
        var orderId = Guid.NewGuid();
        var payment = CreateProcessingPayment(orderId);

        _vnpayGatewayMock.Setup(g => g.ValidateCallback(It.IsAny<IDictionary<string, string>>(), It.IsAny<string>()))
            .Returns(true);
        _paymentRepositoryMock.Setup(r => r.GetByOrderIdAsync(orderId))
            .ReturnsAsync(payment);

        var parameters = new Dictionary<string, string>
        {
            { "vnp_SecureHash", "valid_hash" },
            { "vnp_TxnRef", orderId.ToString() },
            { "vnp_ResponseCode", "24" },
            { "vnp_TransactionNo", "" },
            { "vnp_OrderInfo", "Customer cancelled payment" }
        };

        var command = new ProcessPaymentCallbackCommand("VNPay", parameters);

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        // EN: Failed response code should mark payment as failed.
        // VI: Ma phan hoi that bai nen danh dau payment la that bai.
        result.Should().NotBeNull();
        result.Success.Should().BeFalse();
        result.Status.Should().Be("Failed");
        result.ErrorMessage.Should().Contain("24");

        _paymentRepositoryMock.Verify(r => r.Update(payment), Times.Once);
        _unitOfWorkMock.Verify(u => u.SaveEntitiesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task Handle_WithInvalidTransactionReference_ShouldReturnInvalidReference()
    {
        // Arrange
        _vnpayGatewayMock.Setup(g => g.ValidateCallback(It.IsAny<IDictionary<string, string>>(), It.IsAny<string>()))
            .Returns(true);

        var parameters = new Dictionary<string, string>
        {
            { "vnp_SecureHash", "valid_hash" },
            { "vnp_TxnRef", "not-a-valid-guid" },
            { "vnp_ResponseCode", "00" }
        };

        var command = new ProcessPaymentCallbackCommand("VNPay", parameters);

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Success.Should().BeFalse();
        result.Status.Should().Be("InvalidReference");
        result.ErrorMessage.Should().Contain("Invalid transaction reference");
    }

    [Fact]
    public async Task Handle_WithUnsupportedGateway_ShouldThrowWalletDomainException()
    {
        // Arrange
        var parameters = new Dictionary<string, string>
        {
            { "vnp_SecureHash", "hash" },
            { "vnp_TxnRef", Guid.NewGuid().ToString() }
        };

        var command = new ProcessPaymentCallbackCommand("MoMo", parameters);

        // Act
        var action = () => _handler.Handle(command, CancellationToken.None);

        // Assert
        await action.Should().ThrowAsync<WalletDomainException>()
            .WithMessage("*MoMo*not supported*");
    }
}
