using FluentAssertions;
using PromotionService.Domain.AggregatesModel.CampaignAggregate;
using PromotionService.Domain.Exceptions;
using Xunit;

namespace PromotionService.UnitTests.Domain;

/// <summary>
/// EN: Unit tests for Voucher entity.
/// VI: Unit tests cho Voucher entity.
/// </summary>
public class VoucherEntityTests
{
    private const int DefaultValidityDays = 30;

    #region Helper Methods

    private static Voucher CreateAvailableVoucher() => new Voucher(
        campaignId: Guid.NewGuid(),
        code: "TEST-VOUCHER-001",
        faceValue: 100_000m,
        validityDays: DefaultValidityDays);

    private static Voucher CreateClaimedVoucher()
    {
        var voucher = CreateAvailableVoucher();
        voucher.Claim(Guid.NewGuid(), DefaultValidityDays);
        return voucher;
    }

    #endregion

    #region Constructor Tests

    [Fact]
    public void Constructor_ValidParameters_CreatesVoucherInAvailableStatus()
    {
        // Arrange & Act
        var voucher = CreateAvailableVoucher();

        // Assert
        voucher.Id.Should().NotBeEmpty();
        voucher.Code.Should().Be("TEST-VOUCHER-001");
        voucher.FaceValue.Should().Be(100_000m);
        voucher.RemainingValue.Should().Be(100_000m);
        voucher.StatusId.Should().Be(VoucherStatus.Available.Id);
        voucher.OwnerId.Should().BeNull();
    }

    #endregion

    #region Claim Tests

    [Fact]
    public void Claim_AvailableVoucher_SetsOwnerAndClaimedStatus()
    {
        // Arrange
        var voucher = CreateAvailableVoucher();
        var userId = Guid.NewGuid();

        // Act
        voucher.Claim(userId, DefaultValidityDays);

        // Assert
        voucher.OwnerId.Should().Be(userId);
        voucher.StatusId.Should().Be(VoucherStatus.Claimed.Id);
        voucher.ClaimedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
        voucher.ExpiresAt.Should().BeCloseTo(DateTime.UtcNow.AddDays(DefaultValidityDays), TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void Claim_AlreadyClaimedVoucher_ThrowsException()
    {
        // Arrange
        var voucher = CreateClaimedVoucher();

        // Act & Assert
        var act = () => voucher.Claim(Guid.NewGuid(), DefaultValidityDays);
        act.Should().Throw<VoucherAlreadyClaimedException>();
    }

    #endregion

    #region Redeem Tests

    [Fact]
    public void Redeem_FullValue_SetsStatusToFullyRedeemed()
    {
        // Arrange
        var voucher = CreateClaimedVoucher();

        // Act
        voucher.Redeem(100_000m); // Full face value

        // Assert
        voucher.RemainingValue.Should().Be(0);
        voucher.StatusId.Should().Be(VoucherStatus.FullyRedeemed.Id);
        voucher.RedeemedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void Redeem_PartialValue_SetsStatusToPartiallyRedeemed()
    {
        // Arrange
        var voucher = CreateClaimedVoucher();

        // Act
        voucher.Redeem(50_000m); // Half

        // Assert
        voucher.RemainingValue.Should().Be(50_000m);
        voucher.StatusId.Should().Be(VoucherStatus.PartiallyRedeemed.Id);
    }

    [Fact]
    public void Redeem_MoreThanRemainingValue_CapsAtRemainingValue()
    {
        // Arrange
        var voucher = CreateClaimedVoucher();

        // Act - Request more than available
        var actualRedeemed = voucher.Redeem(150_000m);

        // Assert - Only redeems what's available
        actualRedeemed.Should().Be(100_000m);
        voucher.RemainingValue.Should().Be(0);
        voucher.StatusId.Should().Be(VoucherStatus.FullyRedeemed.Id);
    }

    [Fact]
    public void Redeem_AvailableVoucher_ThrowsException()
    {
        // Arrange
        var voucher = CreateAvailableVoucher(); // Not claimed

        // Act & Assert
        var act = () => voucher.Redeem(50_000m);
        act.Should().Throw<PromotionDomainException>()
            .WithMessage("*not been claimed*");
    }

    #endregion

    #region Expire Tests

    [Fact]
    public void Expire_ClaimedVoucher_SetsStatusToExpired()
    {
        // Arrange
        var voucher = CreateClaimedVoucher();

        // Act
        voucher.Expire();

        // Assert
        voucher.StatusId.Should().Be(VoucherStatus.Expired.Id);
    }

    [Fact]
    public void Expire_FullyRedeemedVoucher_DoesNotChangeStatus()
    {
        // Arrange
        var voucher = CreateClaimedVoucher();
        voucher.Redeem(100_000m);

        // Act
        voucher.Expire();

        // Assert - Status remains FullyRedeemed
        voucher.StatusId.Should().Be(VoucherStatus.FullyRedeemed.Id);
    }

    #endregion

    #region Validation Tests

    [Fact]
    public void IsValidForRedemption_ClaimedNotExpired_ReturnsTrue()
    {
        // Arrange
        var voucher = CreateClaimedVoucher();

        // Act & Assert
        voucher.IsValidForRedemption().Should().BeTrue();
    }

    [Fact]
    public void IsValidForRedemption_FullyRedeemed_ReturnsFalse()
    {
        // Arrange
        var voucher = CreateClaimedVoucher();
        voucher.Redeem(100_000m);

        // Act & Assert
        voucher.IsValidForRedemption().Should().BeFalse();
    }

    #endregion
}
