namespace WalletService.UnitTests.Application;

using FluentAssertions;
using Microsoft.Extensions.Logging;
using NSubstitute;
using WalletService.API.Application.Commands;
using WalletService.Domain.AggregatesModel.WalletAggregate;
using WalletService.Domain.Exceptions;
using WalletService.Domain.SeedWork;
using Xunit;

public class EscrowCommandHandlersTests
{
    private readonly IWalletRepository _walletRepository;
    private readonly ILogger<CreateHoldCommandHandler> _createLogger;
    private readonly ILogger<ExecuteHoldCommandHandler> _executeLogger;
    private readonly IUnitOfWork _unitOfWork;

    public EscrowCommandHandlersTests()
    {
        _walletRepository = Substitute.For<IWalletRepository>();
        _unitOfWork = Substitute.For<IUnitOfWork>();
        _walletRepository.UnitOfWork.Returns(_unitOfWork);
        _unitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>()).Returns(true);

        _createLogger = Substitute.For<ILogger<CreateHoldCommandHandler>>();
        _executeLogger = Substitute.For<ILogger<ExecuteHoldCommandHandler>>();
    }

    [Fact]
    public async Task CreateHold_ValidCommand_ShouldSucceed()
    {
        // Arrange
        var walletId = Guid.NewGuid();
        var userId = Guid.NewGuid();
        var wallet = new Wallet(userId, CurrencyType.VND);
        wallet.Deposit(1000m, CurrencyType.VND, "Init");
        
        // Mock repository to return our wallet when searching by UserId (CreateHoldCommand uses UserId)
        _walletRepository.GetByUserIdAsync(userId).Returns(wallet);

        var handler = new CreateHoldCommandHandler(_walletRepository, _createLogger);
        var command = new CreateHoldCommand(
            userId,
            100m,
            "VND",
            "CAMPAIGN",
            Guid.NewGuid(),
            "Test Hold");

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

        // Assert
        result.Should().NotBeNull();
        result.RemainingAmount.Should().Be(100m);
        wallet.GetBalance(CurrencyType.VND).Should().Be(900m);
        
        _walletRepository.Received(1).Update(wallet);
        await _unitOfWork.Received(1).SaveEntitiesAsync(Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task ExecuteHold_ValidCommand_ShouldSucceed()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var wallet = new Wallet(userId, CurrencyType.VND);
        wallet.Deposit(1000m, CurrencyType.VND, "Init");
        var hold = wallet.Hold(100m, CurrencyType.VND, "REF", Guid.NewGuid(), "Desc");
        
        // Mock repository to return wallet by ID (ExecuteHoldCommand uses WalletId)
        _walletRepository.GetByIdAsync(wallet.Id).Returns(wallet);

        var handler = new ExecuteHoldCommandHandler(_walletRepository, _executeLogger);
        var command = new ExecuteHoldCommand(
            wallet.Id,
            hold.Id,
            40m,
            "EXEC_REF");

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

        // Assert
        result.ExecutedAmount.Should().Be(40m);
        result.RemainingAmount.Should().Be(60m);
        
        await _unitOfWork.Received(1).SaveEntitiesAsync(Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task ExecuteHold_InsufficientHoldAmount_ShouldThrow()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var wallet = new Wallet(userId, CurrencyType.VND);
        wallet.Deposit(1000m, CurrencyType.VND, "Init");
        var hold = wallet.Hold(100m, CurrencyType.VND, "REF", Guid.NewGuid(), "Desc");

        _walletRepository.GetByIdAsync(wallet.Id).Returns(wallet);

        var handler = new ExecuteHoldCommandHandler(_walletRepository, _executeLogger);
        var command = new ExecuteHoldCommand(
            wallet.Id,
            hold.Id,
            150m, // Try to execute more than held
            "EXEC_REF");

        // Act & Assert
        await Assert.ThrowsAsync<WalletDomainException>(() =>
            handler.Handle(command, CancellationToken.None));
    }
}
