using FluentAssertions;
using Microsoft.Extensions.Logging;
using NSubstitute;
using PromotionService.API.Application.Commands;
using PromotionService.API.Application.Services;
using PromotionService.Domain.AggregatesModel.CampaignAggregate;
using PromotionService.Domain.Exceptions;
using PromotionService.Domain.SeedWork;
using Xunit;

namespace PromotionService.UnitTests.Application;

/// <summary>
/// EN: Unit tests for CreateCampaignCommandHandler.
/// VI: Unit tests cho CreateCampaignCommandHandler.
/// </summary>
public class CreateCampaignCommandHandlerTests
{
    private readonly ICampaignRepository _campaignRepository;
    private readonly IWalletServiceClient _walletService;
    private readonly ILogger<CreateCampaignCommandHandler> _logger;
    private readonly CreateCampaignCommandHandler _handler;

    public CreateCampaignCommandHandlerTests()
    {
        _campaignRepository = Substitute.For<ICampaignRepository>();
        _walletService = Substitute.For<IWalletServiceClient>();
        _logger = Substitute.For<ILogger<CreateCampaignCommandHandler>>();

        // Setup UnitOfWork
        var unitOfWork = Substitute.For<IUnitOfWork>();
        unitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>()).Returns(true);
        _campaignRepository.UnitOfWork.Returns(unitOfWork);

        _handler = new CreateCampaignCommandHandler(
            _campaignRepository,
            _walletService,
            _logger);
    }

    private static CreateCampaignCommand CreateValidCommand() => new CreateCampaignCommand(
        MerchantId: Guid.NewGuid(),
        MerchantWalletId: Guid.NewGuid(),
        Name: "Test Campaign",
        Description: "Test Description",
        BackingAssetType: "Currency",
        BackingAssetCode: "VND",
        FaceValue: 100_000m,
        AcquisitionType: "Free",
        AcquisitionPrice: 0m,
        TotalVouchers: 100,
        StartDate: DateTime.UtcNow.AddDays(1),
        EndDate: DateTime.UtcNow.AddDays(30),
        VoucherValidityDays: 30,
        MaxPerUser: 1);

    [Fact]
    public async Task Handle_ValidCommand_CreatesCampaignAndReturnsDto()
    {
        // Arrange
        var command = CreateValidCommand();
        var holdResult = new HoldResult(
            HoldId: Guid.NewGuid(),
            WalletId: command.MerchantWalletId,
            Amount: command.FaceValue * command.TotalVouchers,
            CurrencyType: command.BackingAssetCode,
            ReferenceType: "CAMPAIGN",
            ReferenceId: Guid.NewGuid(),
            Status: "Active");

        _walletService.CreateHoldAsync(
            Arg.Any<Guid>(),
            Arg.Any<decimal>(),
            Arg.Any<string>(),
            Arg.Any<string>(),
            Arg.Any<Guid>(),
            Arg.Any<string>(),
            Arg.Any<CancellationToken>())
            .Returns(holdResult);

        _campaignRepository.Add(Arg.Any<Campaign>())
            .Returns(callInfo => callInfo.Arg<Campaign>());

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

        // Assert
        result.Should().NotBeNull();
        result.Name.Should().Be(command.Name);
        result.TotalVouchers.Should().Be(command.TotalVouchers);
        result.Status.Should().Be("Draft");

        // Verify repository called
        _campaignRepository.Received(1).Add(Arg.Is<Campaign>(c => c.Name == command.Name));
        await _campaignRepository.UnitOfWork.Received(1).SaveEntitiesAsync(Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_ValidCommand_CallsWalletServiceToCreateEscrow()
    {
        // Arrange
        var command = CreateValidCommand();
        var expectedEscrowAmount = command.FaceValue * command.TotalVouchers;

        _walletService.CreateHoldAsync(
            Arg.Any<Guid>(),
            Arg.Any<decimal>(),
            Arg.Any<string>(),
            Arg.Any<string>(),
            Arg.Any<Guid>(),
            Arg.Any<string>(),
            Arg.Any<CancellationToken>())
            .Returns(new HoldResult(
                Guid.NewGuid(),
                command.MerchantWalletId,
                expectedEscrowAmount,
                "VND",
                "CAMPAIGN",
                Guid.NewGuid(),
                "Active"));

        _campaignRepository.Add(Arg.Any<Campaign>())
            .Returns(callInfo => callInfo.Arg<Campaign>());

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

        // Assert - Verify wallet service was called with correct escrow amount
        await _walletService.Received(1).CreateHoldAsync(
            command.MerchantWalletId,
            expectedEscrowAmount,
            command.BackingAssetCode,
            "CAMPAIGN",
            Arg.Any<Guid>(),
            Arg.Is<string>(s => s.Contains(command.Name)),
            Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_InvalidAcquisitionType_ThrowsDomainException()
    {
        // Arrange
        var command = new CreateCampaignCommand(
            MerchantId: Guid.NewGuid(),
            MerchantWalletId: Guid.NewGuid(),
            Name: "Test Campaign",
            Description: null,
            BackingAssetType: "Currency",
            BackingAssetCode: "VND",
            FaceValue: 100_000m,
            AcquisitionType: "InvalidType", // Invalid!
            AcquisitionPrice: 0m,
            TotalVouchers: 100,
            StartDate: DateTime.UtcNow.AddDays(1),
            EndDate: DateTime.UtcNow.AddDays(30));

        // Act & Assert
        var act = async () => await _handler.Handle(command, CancellationToken.None);
        await act.Should().ThrowAsync<PromotionDomainException>()
            .WithMessage("*Invalid acquisition type*");

        // Verify wallet service was NOT called
        await _walletService.DidNotReceive().CreateHoldAsync(
            Arg.Any<Guid>(),
            Arg.Any<decimal>(),
            Arg.Any<string>(),
            Arg.Any<string>(),
            Arg.Any<Guid>(),
            Arg.Any<string>(),
            Arg.Any<CancellationToken>());
    }
}
