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

namespace PromotionService.UnitTests.Application;

/// <summary>
/// EN: Unit tests for ClaimVoucherCommandHandler.
/// VI: Unit tests cho ClaimVoucherCommandHandler.
/// </summary>
public class ClaimVoucherCommandHandlerTests
{
    private readonly ICampaignRepository _campaignRepository;
    private readonly ILogger<ClaimVoucherCommandHandler> _logger;
    private readonly ClaimVoucherCommandHandler _handler;

    public ClaimVoucherCommandHandlerTests()
    {
        _campaignRepository = Substitute.For<ICampaignRepository>();
        _logger = Substitute.For<ILogger<ClaimVoucherCommandHandler>>();

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

        _handler = new ClaimVoucherCommandHandler(_campaignRepository, _logger);
    }

    private static Campaign CreateActiveCampaign()
    {
        var campaign = new Campaign(
            merchantId: Guid.NewGuid(),
            name: "Free Voucher Campaign",
            description: null,
            backingAssetType: AssetType.Currency,
            backingAssetCode: "VND",
            faceValue: 50_000m,
            acquisitionType: AcquisitionType.Free,
            acquisitionPrice: 0m,
            totalVouchers: 100,
            startDate: DateTime.UtcNow.AddDays(-1),
            endDate: DateTime.UtcNow.AddDays(30),
            voucherValidityDays: 30,
            maxPerUser: 1);

        campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
        campaign.GenerateVouchers(100);
        campaign.Activate();
        return campaign;
    }

    [Fact]
    public async Task Handle_FreeCampaign_ClaimsVoucherSuccessfully()
    {
        // Arrange
        var campaign = CreateActiveCampaign();
        var userId = Guid.NewGuid();
        var command = new ClaimVoucherCommand(campaign.Id, userId);

        _campaignRepository.GetByIdAsync(campaign.Id)
            .Returns(campaign);

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

        // Assert
        result.Should().NotBeNull();
        result.OwnerId.Should().Be(userId);
        result.Status.Should().Be("Claimed");
        result.RemainingValue.Should().Be(50_000m);

        _campaignRepository.Received(1).Update(campaign);
        await _campaignRepository.UnitOfWork.Received(1).SaveEntitiesAsync(Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_NonFreeCampaign_ThrowsDomainException()
    {
        // Arrange
        var campaign = new Campaign(
            merchantId: Guid.NewGuid(),
            name: "Paid Voucher Campaign",
            description: null,
            backingAssetType: AssetType.Currency,
            backingAssetCode: "VND",
            faceValue: 100_000m,
            acquisitionType: AcquisitionType.Purchase, // Not free!
            acquisitionPrice: 50_000m,
            totalVouchers: 100,
            startDate: DateTime.UtcNow.AddDays(-1),
            endDate: DateTime.UtcNow.AddDays(30),
            voucherValidityDays: 30,
            maxPerUser: 1);

        campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
        campaign.GenerateVouchers(100);
        campaign.Activate();

        _campaignRepository.GetByIdAsync(campaign.Id).Returns(campaign);

        var command = new ClaimVoucherCommand(campaign.Id, Guid.NewGuid());

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

    [Fact]
    public async Task Handle_CampaignNotFound_ThrowsDomainException()
    {
        // Arrange
        var nonExistentId = Guid.NewGuid();
        _campaignRepository.GetByIdAsync(nonExistentId).Returns((Campaign?)null);

        var command = new ClaimVoucherCommand(nonExistentId, Guid.NewGuid());

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