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

namespace PromotionService.UnitTests.Domain;

/// <summary>
/// EN: Unit tests for Campaign aggregate root.
/// VI: Unit tests cho Campaign aggregate root.
/// </summary>
public class CampaignAggregateTests
{
    #region Helper Methods

    /// <summary>
    /// EN: Creates a campaign with future start date (for constructor/lifecycle tests).
    /// VI: Tạo campaign với ngày bắt đầu trong tương lai (cho constructor/lifecycle tests).
    /// </summary>
    private static Campaign CreateValidCampaign() => new Campaign(
        merchantId: Guid.NewGuid(),
        name: "Test Campaign",
        description: "Test Description",
        backingAssetType: AssetType.Currency,
        backingAssetCode: "VND",
        faceValue: 100_000m,
        acquisitionType: AcquisitionType.Free,
        acquisitionPrice: 0m,
        totalVouchers: 100,
        startDate: DateTime.UtcNow.AddDays(1),
        endDate: DateTime.UtcNow.AddDays(30),
        voucherValidityDays: 30,
        maxPerUser: 1);

    /// <summary>
    /// EN: Creates a campaign with past start date (for issuing vouchers tests).
    /// VI: Tạo campaign với ngày bắt đầu trong quá khứ (cho tests phát hành voucher).
    /// </summary>
    private static Campaign CreateActiveCampaign() => new Campaign(
        merchantId: Guid.NewGuid(),
        name: "Active Campaign",
        description: "Active Description",
        backingAssetType: AssetType.Currency,
        backingAssetCode: "VND",
        faceValue: 100_000m,
        acquisitionType: AcquisitionType.Free,
        acquisitionPrice: 0m,
        totalVouchers: 100,
        startDate: DateTime.UtcNow.AddDays(-1), // Started yesterday
        endDate: DateTime.UtcNow.AddDays(30),
        voucherValidityDays: 30,
        maxPerUser: 1);

    #endregion

    #region Constructor Tests

    [Fact]
    public void Constructor_ValidParameters_CreatesCampaignInDraftStatus()
    {
        // Arrange & Act
        var campaign = CreateValidCampaign();

        // Assert
        campaign.Id.Should().NotBeEmpty();
        campaign.StatusId.Should().Be(CampaignStatus.Draft.Id);
        campaign.TotalVouchers.Should().Be(100);
        campaign.IssuedVouchers.Should().Be(0);
        campaign.Vouchers.Should().BeEmpty();
    }

    [Fact]
    public void Constructor_ZeroTotalVouchers_ThrowsException()
    {
        // Arrange & Act
        var act = () => new Campaign(
            merchantId: Guid.NewGuid(),
            name: "Test Campaign",
            description: null,
            backingAssetType: AssetType.Currency,
            backingAssetCode: "VND",
            faceValue: 100_000m,
            acquisitionType: AcquisitionType.Free,
            acquisitionPrice: 0m,
            totalVouchers: 0, // Invalid!
            startDate: DateTime.UtcNow.AddDays(1),
            endDate: DateTime.UtcNow.AddDays(30),
            voucherValidityDays: 30,
            maxPerUser: 1);

        // Assert
        act.Should().Throw<PromotionDomainException>()
            .WithMessage("*Total vouchers*");
    }

    [Fact]
    public void Constructor_EndDateBeforeStartDate_ThrowsException()
    {
        // Arrange & Act
        var act = () => new Campaign(
            merchantId: Guid.NewGuid(),
            name: "Test Campaign",
            description: null,
            backingAssetType: AssetType.Currency,
            backingAssetCode: "VND",
            faceValue: 100_000m,
            acquisitionType: AcquisitionType.Free,
            acquisitionPrice: 0m,
            totalVouchers: 100,
            startDate: DateTime.UtcNow.AddDays(30),
            endDate: DateTime.UtcNow.AddDays(1), // Before start!
            voucherValidityDays: 30,
            maxPerUser: 1);

        // Assert
        act.Should().Throw<PromotionDomainException>()
            .WithMessage("*End date*");
    }

    #endregion

    #region Lifecycle Tests

    [Fact]
    public void Activate_DraftCampaign_ChangesStatusToActive()
    {
        // Arrange
        var campaign = CreateValidCampaign();
        campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());

        // Act
        campaign.Activate();

        // Assert
        campaign.StatusId.Should().Be(CampaignStatus.Active.Id);
    }

    [Fact]
    public void Activate_WithoutEscrow_ThrowsException()
    {
        // Arrange
        var campaign = CreateValidCampaign();

        // Act & Assert
        var act = () => campaign.Activate();
        act.Should().Throw<PromotionDomainException>()
            .WithMessage("*escrow*");
    }

    [Fact]
    public void Pause_ActiveCampaign_ChangesStatusToPaused()
    {
        // Arrange
        var campaign = CreateValidCampaign();
        campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
        campaign.Activate();

        // Act
        campaign.Pause();

        // Assert
        campaign.StatusId.Should().Be(CampaignStatus.Paused.Id);
    }

    [Fact]
    public void Cancel_DraftCampaign_ChangesStatusToCancelled()
    {
        // Arrange
        var campaign = CreateValidCampaign();

        // Act
        campaign.Cancel();

        // Assert
        campaign.StatusId.Should().Be(CampaignStatus.Cancelled.Id);
    }

    [Fact]
    public void Cancel_ActiveCampaign_ChangesStatusToCancelled()
    {
        // Arrange
        var campaign = CreateValidCampaign();
        campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
        campaign.Activate();

        // Act
        campaign.Cancel();

        // Assert
        campaign.StatusId.Should().Be(CampaignStatus.Cancelled.Id);
        campaign.DomainEvents.Should().ContainSingle(e => e is CampaignCancelledDomainEvent);
    }

    #endregion

    #region Voucher Generation Tests

    [Fact]
    public void GenerateVouchers_ValidCount_CreatesVouchersWithUniqueCodes()
    {
        // Arrange
        var campaign = CreateValidCampaign();

        // Act
        campaign.GenerateVouchers(10);

        // Assert
        campaign.Vouchers.Should().HaveCount(10);
        campaign.Vouchers.Select(v => v.Code).Should().OnlyHaveUniqueItems();
        campaign.Vouchers.All(v => v.FaceValue == campaign.FaceValue).Should().BeTrue();
    }

    [Fact]
    public void GenerateVouchers_ExceedsTotalLimit_ThrowsException()
    {
        // Arrange
        var campaign = CreateValidCampaign(); // totalVouchers = 100

        // Act & Assert
        var act = () => campaign.GenerateVouchers(150);
        act.Should().Throw<PromotionDomainException>()
            .WithMessage("*Maximum*");
    }

    #endregion

    #region Issue Voucher Tests

    [Fact]
    public void IssueVoucher_ActiveCampaign_ReturnsClaimedVoucher()
    {
        // Arrange
        var campaign = CreateActiveCampaign();
        campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
        campaign.GenerateVouchers(10);
        campaign.Activate();
        var userId = Guid.NewGuid();

        // Act
        var voucher = campaign.IssueVoucher(userId);

        // Assert
        voucher.Should().NotBeNull();
        voucher.OwnerId.Should().Be(userId);
        voucher.StatusId.Should().Be(VoucherStatus.Claimed.Id);
        campaign.IssuedVouchers.Should().Be(1);
    }

    [Fact]
    public void IssueVoucher_DraftCampaign_ThrowsException()
    {
        // Arrange
        var campaign = CreateValidCampaign();
        campaign.GenerateVouchers(10);

        // Act & Assert
        var act = () => campaign.IssueVoucher(Guid.NewGuid());
        act.Should().Throw<CampaignNotActiveException>();
    }

    [Fact]
    public void IssueVoucher_NoAvailableVouchers_ThrowsException()
    {
        // Arrange
        var campaign = new Campaign(
            merchantId: Guid.NewGuid(),
            name: "Small Campaign",
            description: null,
            backingAssetType: AssetType.Currency,
            backingAssetCode: "VND",
            faceValue: 50_000m,
            acquisitionType: AcquisitionType.Free,
            acquisitionPrice: 0m,
            totalVouchers: 1,
            startDate: DateTime.UtcNow.AddDays(-1),
            endDate: DateTime.UtcNow.AddDays(30),
            voucherValidityDays: 30,
            maxPerUser: 1);
        campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
        campaign.GenerateVouchers(1);
        campaign.Activate();
        campaign.IssueVoucher(Guid.NewGuid()); // Take the only one

        // Act & Assert
        var act = () => campaign.IssueVoucher(Guid.NewGuid());
        act.Should().Throw<PromotionDomainException>()
            .WithMessage("*voucher*");
    }

    #endregion

    #region Domain Events Tests

    [Fact]
    public void Activate_AddsCampaignActivatedDomainEvent()
    {
        // Arrange
        var campaign = CreateValidCampaign();
        campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());

        // Act
        campaign.Activate();

        // Assert
        campaign.DomainEvents.Should().ContainSingle(e => e is CampaignActivatedDomainEvent);
    }

    [Fact]
    public void IssueVoucher_AddsVoucherClaimedDomainEvent()
    {
        // Arrange
        var campaign = CreateActiveCampaign();
        campaign.SetEscrowHold(Guid.NewGuid(), Guid.NewGuid());
        campaign.GenerateVouchers(10);
        campaign.Activate();

        // Act
        campaign.IssueVoucher(Guid.NewGuid());

        // Assert
        campaign.DomainEvents.Should().Contain(e => e is VoucherClaimedDomainEvent);
    }

    #endregion
}
