using FluentAssertions;
using MembershipService.API.Application.Queries;
using MembershipService.Domain.AggregatesModel.LevelAggregate;
using MembershipService.Domain.AggregatesModel.MemberAggregate;
using NSubstitute;
using Xunit;

namespace MembershipService.UnitTests.Handlers;

/// <summary>
/// EN: Unit tests for GetMemberProgressQueryHandler.
/// VI: Unit tests cho GetMemberProgressQueryHandler.
/// </summary>
public class GetMemberProgressQueryHandlerTests
{
    private readonly IMemberRepository _memberRepository;
    private readonly ILevelDefinitionRepository _levelDefinitionRepository;
    private readonly GetMemberProgressQueryHandler _handler;

    public GetMemberProgressQueryHandlerTests()
    {
        _memberRepository = Substitute.For<IMemberRepository>();
        _levelDefinitionRepository = Substitute.For<ILevelDefinitionRepository>();

        _handler = new GetMemberProgressQueryHandler(
            _memberRepository,
            _levelDefinitionRepository);
    }

    [Fact]
    public async Task Handle_ValidMember_ShouldReturnProgressDto()
    {
        // Arrange
        var memberId = Guid.NewGuid();
        var member = new Member(memberId);
        var levelRules = CreateDefaultLevelRules();

        var query = new GetMemberProgressQuery(memberId);

        _memberRepository.GetByIdAsync(memberId, Arg.Any<CancellationToken>())
            .Returns(member);
        _levelDefinitionRepository.GetAllActiveAsync()
            .Returns(levelRules);

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

        // Assert
        result.Should().NotBeNull();
        result!.MemberId.Should().Be(memberId);
        result.CurrentLevel.Should().Be(1);
        result.CurrentLevelName.Should().Be("Bronze");
        result.CurrentExp.Should().Be(0);
        result.NextLevel.Should().Be(2);
        result.NextLevelName.Should().Be("Silver");
        result.ExpToNextLevel.Should().Be(100);
    }

    [Fact]
    public async Task Handle_MemberNotFound_ShouldReturnNull()
    {
        // Arrange
        var memberId = Guid.NewGuid();
        var query = new GetMemberProgressQuery(memberId);

        _memberRepository.GetByIdAsync(memberId, Arg.Any<CancellationToken>())
            .Returns((Member?)null);

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

        // Assert
        result.Should().BeNull();
    }

    [Fact]
    public async Task Handle_NoLevelRules_ShouldReturnNull()
    {
        // Arrange
        var memberId = Guid.NewGuid();
        var member = new Member(memberId);
        var query = new GetMemberProgressQuery(memberId);

        _memberRepository.GetByIdAsync(memberId, Arg.Any<CancellationToken>())
            .Returns(member);
        _levelDefinitionRepository.GetAllActiveAsync()
            .Returns(new List<LevelDefinition>());

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

        // Assert
        result.Should().BeNull();
    }

    [Fact]
    public async Task Handle_MemberAtMaxLevel_ShouldReturnNullNextLevel()
    {
        // Arrange
        var memberId = Guid.NewGuid();
        var member = new Member(memberId);
        var levelRules = CreateDefaultLevelRules();

        // Add enough EXP to reach max level (Diamond = 1000)
        member.AddExperience(1500, 
            MembershipService.Domain.AggregatesModel.ExperienceAggregate.ExperienceSource.Admin, 
            levelRules);

        var query = new GetMemberProgressQuery(memberId);

        _memberRepository.GetByIdAsync(memberId, Arg.Any<CancellationToken>())
            .Returns(member);
        _levelDefinitionRepository.GetAllActiveAsync()
            .Returns(levelRules);

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

        // Assert
        result.Should().NotBeNull();
        result!.CurrentLevel.Should().Be(5);
        result.CurrentLevelName.Should().Be("Diamond");
        result.NextLevel.Should().BeNull();
        result.NextLevelName.Should().BeNull();
        result.ProgressPercent.Should().Be(100);
    }

    [Fact]
    public async Task Handle_MemberWithPartialProgress_ShouldCalculatePercentCorrectly()
    {
        // Arrange
        var memberId = Guid.NewGuid();
        var member = new Member(memberId);
        var levelRules = CreateDefaultLevelRules();

        // Add 50 EXP (50% progress to Silver which needs 100)
        member.AddExperience(50, 
            MembershipService.Domain.AggregatesModel.ExperienceAggregate.ExperienceSource.Purchase, 
            levelRules);

        var query = new GetMemberProgressQuery(memberId);

        _memberRepository.GetByIdAsync(memberId, Arg.Any<CancellationToken>())
            .Returns(member);
        _levelDefinitionRepository.GetAllActiveAsync()
            .Returns(levelRules);

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

        // Assert
        result.Should().NotBeNull();
        result!.CurrentLevel.Should().Be(1);
        result.CurrentExp.Should().Be(50);
        result.ProgressPercent.Should().Be(50);
        result.ExpToNextLevel.Should().Be(50);
    }

    [Fact]
    public async Task Handle_ShouldReturnBadgeColor()
    {
        // Arrange
        var memberId = Guid.NewGuid();
        var member = new Member(memberId);
        var levelRules = new List<LevelDefinition>
        {
            new(1, "Bronze", 0, "Starting level", null, "#CD7F32"),
            new(2, "Silver", 100, "Reach 100 EXP", null, "#C0C0C0")
        };

        var query = new GetMemberProgressQuery(memberId);

        _memberRepository.GetByIdAsync(memberId, Arg.Any<CancellationToken>())
            .Returns(member);
        _levelDefinitionRepository.GetAllActiveAsync()
            .Returns(levelRules);

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

        // Assert
        result.Should().NotBeNull();
        result!.BadgeColor.Should().Be("#CD7F32");
    }

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