using FluentAssertions;
using MembershipService.Domain.AggregatesModel.ExperienceAggregate;
using MembershipService.Domain.AggregatesModel.LevelAggregate;
using MembershipService.Domain.AggregatesModel.MemberAggregate;
using MembershipService.Domain.Events;
using Xunit;

namespace MembershipService.UnitTests.Domain;

/// <summary>
/// EN: Unit tests for Member aggregate.
/// VI: Unit tests cho Member aggregate.
/// </summary>
public class MemberAggregateTests
{
    [Fact]
    public void CreateMember_WithValidUserId_ShouldCreateMemberWithLevel1()
    {
        // Arrange
        var userId = Guid.NewGuid();

        // Act
        var member = new Member(userId);

        // Assert
        member.Id.Should().Be(userId);
        member.UserId.Should().Be(userId);
        member.CurrentLevel.Should().Be(1);
        member.CurrentExp.Should().Be(0);
        member.TotalExpEarned.Should().Be(0);
        member.CountryCode.Should().Be("VN");
        member.IsDeleted.Should().BeFalse();
        member.DomainEvents.Should().ContainSingle();
    }

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

        // Act
        var member = new Member(userId, "US");

        // Assert
        member.CountryCode.Should().Be("US");
    }

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

        // Act
        var member = new Member(userId, "VN", "Male");

        // Assert
        member.Gender.Should().Be("Male");
        member.CountryCode.Should().Be("VN");
    }

    [Fact]
    public void UpdateGender_ShouldUpdateFieldAndRaiseEvent()
    {
        // Arrange
        var member = new Member(Guid.NewGuid());
        member.ClearDomainEvents();

        // Act
        member.UpdateGender("Female");

        // Assert
        member.Gender.Should().Be("Female");
        member.DomainEvents.Should().ContainSingle();
    }

    [Fact]
    public void UpdateCountryCode_ShouldUpdateFieldAndRaiseEvent()
    {
        // Arrange
        var member = new Member(Guid.NewGuid());
        member.ClearDomainEvents();

        // Act
        member.UpdateCountryCode("US");

        // Assert
        member.CountryCode.Should().Be("US");
        member.DomainEvents.Should().ContainSingle();
    }

    [Fact]
    public void UpdatePreferences_ShouldUpdateFieldAndRaiseEvent()
    {
        // Arrange
        var member = new Member(Guid.NewGuid());
        member.ClearDomainEvents();
        var preferences = "{\"theme\": \"dark\", \"language\": \"vi\"}";

        // Act
        member.UpdatePreferences(preferences);

        // Assert
        member.Preferences.Should().Be(preferences);
        member.DomainEvents.Should().ContainSingle();
    }

    [Fact]
    public void AddExperience_ShouldAddExpAndRaiseEvent()
    {
        // Arrange
        var member = new Member(Guid.NewGuid());
        member.ClearDomainEvents();
        var levelRules = CreateDefaultLevelRules();

        // Act
        var transaction = member.AddExperience(50, ExperienceSource.Purchase, levelRules, "ORDER-123");

        // Assert
        member.CurrentExp.Should().Be(50);
        member.TotalExpEarned.Should().Be(50);
        member.CurrentLevel.Should().Be(1); // Not enough for level 2
        transaction.Points.Should().Be(50);
        transaction.Source.Should().Be(ExperienceSource.Purchase);
        transaction.ReferenceId.Should().Be("ORDER-123");
        member.DomainEvents.Should().ContainSingle(e => e is MemberExperienceAddedDomainEvent);
    }

    [Fact]
    public void AddExperience_ShouldLevelUp_WhenThresholdReached()
    {
        // Arrange
        var member = new Member(Guid.NewGuid());
        member.ClearDomainEvents();
        var levelRules = CreateDefaultLevelRules();

        // Act
        var transaction = member.AddExperience(150, ExperienceSource.Purchase, levelRules);

        // Assert
        member.CurrentExp.Should().Be(150);
        member.CurrentLevel.Should().Be(2); // Silver at 100 EXP
        member.DomainEvents.Should().Contain(e => e is MemberLevelUpDomainEvent);
        member.DomainEvents.Should().Contain(e => e is MemberExperienceAddedDomainEvent);
    }

    [Fact]
    public void AddExperience_ShouldLevelUpMultipleLevels_WhenBigExpGain()
    {
        // Arrange
        var member = new Member(Guid.NewGuid());
        member.ClearDomainEvents();
        var levelRules = CreateDefaultLevelRules();

        // Act
        member.AddExperience(500, ExperienceSource.Admin, levelRules);

        // Assert
        member.CurrentExp.Should().Be(500);
        member.CurrentLevel.Should().Be(3); // Gold at 300 EXP
    }

    [Fact]
    public void AddExperience_WithNegativePoints_ShouldThrow()
    {
        // Arrange
        var member = new Member(Guid.NewGuid());
        var levelRules = CreateDefaultLevelRules();

        // Act & Assert
        var act = () => member.AddExperience(-10, ExperienceSource.Purchase, levelRules);
        act.Should().Throw<ArgumentException>().WithMessage("*positive*");
    }

    [Fact]
    public void Delete_ShouldMarkAsDeleted()
    {
        // Arrange
        var member = new Member(Guid.NewGuid());

        // Act
        member.Delete();

        // Assert
        member.IsDeleted.Should().BeTrue();
    }

    private static IReadOnlyList<LevelDefinition> CreateDefaultLevelRules()
    {
        return new List<LevelDefinition>
        {
            new LevelDefinition(1, "Bronze", 0, "Starting level"),
            new LevelDefinition(2, "Silver", 100, "Reach 100 EXP"),
            new LevelDefinition(3, "Gold", 300, "Reach 300 EXP"),
            new LevelDefinition(4, "Platinum", 600, "Reach 600 EXP"),
            new LevelDefinition(5, "Diamond", 1000, "Reach 1000 EXP")
        };
    }
}
