// EN: Unit tests for CreatePaymentCommandHandler - payment creation with gateway integration.
// VI: Unit tests cho CreatePaymentCommandHandler - tao thanh toan voi tich hop cong thanh toan.

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

namespace WalletService.UnitTests.Application.Commands;

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

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

        _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);
        _paymentRepositoryMock.Setup(r => r.Add(It.IsAny<Payment>()))
            .Returns((Payment p) => p);

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

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

    [Fact]
    public async Task Handle_WithValidPayment_ShouldCreatePayment()
    {
        // Arrange
        _vnpayGatewayMock.Setup(g => g.CreatePaymentAsync(It.IsAny<PaymentRequest>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new PaymentResult(
                Success: true,
                TransactionId: "TXN123",
                PaymentUrl: "https://vnpay.vn/pay?txn=TXN123"));

        var command = new CreatePaymentCommand(
            OrderId: Guid.NewGuid(),
            Amount: 100_000m,
            Currency: "VND",
            GatewayName: "VNPay",
            ReturnUrl: "https://goodgo.vn/payment/return",
            IpAddress: "127.0.0.1");

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

        // Assert
        result.Should().NotBeNull();
        result.PaymentId.Should().NotBeEmpty();
        result.OrderId.Should().Be(command.OrderId);
        result.Amount.Should().Be(100_000m);
        result.Currency.Should().Be("VND");
        result.GatewayName.Should().Be("VNPay");
        result.Status.Should().Be("Processing");
        result.PaymentUrl.Should().Be("https://vnpay.vn/pay?txn=TXN123");

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

    [Fact]
    public async Task Handle_WithInvalidGateway_ShouldThrowWalletDomainException()
    {
        // Arrange
        var command = new CreatePaymentCommand(
            OrderId: Guid.NewGuid(),
            Amount: 50_000m,
            Currency: "VND",
            GatewayName: "NonExistentGateway",
            ReturnUrl: "https://goodgo.vn/payment/return",
            IpAddress: "127.0.0.1");

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

        // Assert
        // EN: Should throw WalletDomainException for unsupported gateway.
        // VI: Nen throw WalletDomainException cho cong thanh toan khong ho tro.
        await action.Should().ThrowAsync<WalletDomainException>()
            .WithMessage("*NonExistentGateway*not supported*");
    }

    [Fact]
    public async Task Handle_WithGatewayFailure_ShouldCreatePaymentWithFailedStatus()
    {
        // Arrange
        _vnpayGatewayMock.Setup(g => g.CreatePaymentAsync(It.IsAny<PaymentRequest>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new PaymentResult(
                Success: false,
                ErrorCode: "TIMEOUT",
                ErrorMessage: "Gateway timeout"));

        var command = new CreatePaymentCommand(
            OrderId: Guid.NewGuid(),
            Amount: 200_000m,
            Currency: "VND",
            GatewayName: "VNPay",
            ReturnUrl: "https://goodgo.vn/payment/return",
            IpAddress: "127.0.0.1");

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

        // Assert
        // EN: Should create the payment entity but mark it as failed.
        // VI: Nen tao payment entity nhung danh dau la that bai.
        result.Should().NotBeNull();
        result.Status.Should().Be("Failed");
        result.PaymentUrl.Should().BeNull();

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

    [Fact]
    public void Handle_WithZeroAmount_ShouldThrowWalletDomainException()
    {
        // Arrange
        // EN: Payment entity constructor validates amount > 0.
        // VI: Constructor cua Payment entity validate amount > 0.
        var command = new CreatePaymentCommand(
            OrderId: Guid.NewGuid(),
            Amount: 0m,
            Currency: "VND",
            GatewayName: "VNPay",
            ReturnUrl: "https://goodgo.vn/payment/return",
            IpAddress: "127.0.0.1");

        _vnpayGatewayMock.Setup(g => g.CreatePaymentAsync(It.IsAny<PaymentRequest>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new PaymentResult(Success: true, TransactionId: "TXN123", PaymentUrl: "https://vnpay.vn/pay"));

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

        // Assert
        action.Should().ThrowAsync<WalletDomainException>()
            .WithMessage("*amount*greater than zero*");
    }

    [Fact]
    public void Handle_WithNegativeAmount_ShouldThrowWalletDomainException()
    {
        // Arrange
        var command = new CreatePaymentCommand(
            OrderId: Guid.NewGuid(),
            Amount: -100_000m,
            Currency: "VND",
            GatewayName: "VNPay",
            ReturnUrl: "https://goodgo.vn/payment/return",
            IpAddress: "127.0.0.1");

        _vnpayGatewayMock.Setup(g => g.CreatePaymentAsync(It.IsAny<PaymentRequest>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new PaymentResult(Success: true, TransactionId: "TXN123", PaymentUrl: "https://vnpay.vn/pay"));

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

        // Assert
        action.Should().ThrowAsync<WalletDomainException>()
            .WithMessage("*amount*greater than zero*");
    }

    [Fact]
    public async Task Handle_ShouldRaiseDomainEvent()
    {
        // Arrange
        _vnpayGatewayMock.Setup(g => g.CreatePaymentAsync(It.IsAny<PaymentRequest>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new PaymentResult(
                Success: true,
                TransactionId: "TXN456",
                PaymentUrl: "https://vnpay.vn/pay?txn=TXN456"));

        Payment? capturedPayment = null;
        _paymentRepositoryMock.Setup(r => r.Add(It.IsAny<Payment>()))
            .Callback<Payment>(p => capturedPayment = p)
            .Returns((Payment p) => p);

        var command = new CreatePaymentCommand(
            OrderId: Guid.NewGuid(),
            Amount: 150_000m,
            Currency: "VND",
            GatewayName: "VNPay",
            ReturnUrl: "https://goodgo.vn/payment/return",
            IpAddress: "127.0.0.1");

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

        // Assert
        // EN: Payment constructor raises PaymentCreatedDomainEvent.
        // VI: Constructor cua Payment phat ra PaymentCreatedDomainEvent.
        capturedPayment.Should().NotBeNull();
        capturedPayment!.DomainEvents.Should().NotBeEmpty();
        capturedPayment.DomainEvents.Should().ContainItemsAssignableTo<PaymentCreatedDomainEvent>();
    }

    [Fact]
    public async Task Handle_WithSuccessfulGateway_ShouldSetPaymentUrlAndTransactionId()
    {
        // Arrange
        var expectedUrl = "https://vnpay.vn/pay?txn=ABC789";
        var expectedTxnId = "ABC789";

        _vnpayGatewayMock.Setup(g => g.CreatePaymentAsync(It.IsAny<PaymentRequest>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new PaymentResult(
                Success: true,
                TransactionId: expectedTxnId,
                PaymentUrl: expectedUrl));

        var command = new CreatePaymentCommand(
            OrderId: Guid.NewGuid(),
            Amount: 300_000m,
            Currency: "VND",
            GatewayName: "VNPay",
            ReturnUrl: "https://goodgo.vn/payment/return",
            IpAddress: "192.168.1.1");

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

        // Assert
        result.PaymentUrl.Should().Be(expectedUrl);
        result.Status.Should().Be("Processing");
    }
}
