using FluentAssertions;
using Microsoft.Extensions.Logging;
using NSubstitute;
using SocialService.API.Application.Commands;
using SocialService.Domain.AggregatesModel.RelationshipAggregate;
using SocialService.Domain.AggregatesModel.UserBlockAggregate;
using SocialService.Domain.Exceptions;
using SocialService.Domain.SeedWork;
using Xunit;

namespace SocialService.UnitTests.Application.Handlers;

/// <summary>
/// EN: Unit tests for SendFriendRequestCommandHandler.
/// VI: Unit tests cho SendFriendRequestCommandHandler.
/// </summary>
public class SendFriendRequestCommandHandlerTests
{
    private readonly IRelationshipRepository _relationshipRepository;
    private readonly IUserBlockRepository _userBlockRepository;
    private readonly ILogger<SendFriendRequestCommandHandler> _logger;
    private readonly SendFriendRequestCommandHandler _handler;

    public SendFriendRequestCommandHandlerTests()
    {
        _relationshipRepository = Substitute.For<IRelationshipRepository>();
        _userBlockRepository = Substitute.For<IUserBlockRepository>();
        _logger = Substitute.For<ILogger<SendFriendRequestCommandHandler>>();

        // EN: Setup UnitOfWork mock
        // VI: Setup mock cho UnitOfWork
        var unitOfWork = Substitute.For<IUnitOfWork>();
        unitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>()).Returns(true);
        _relationshipRepository.UnitOfWork.Returns(unitOfWork);

        _handler = new SendFriendRequestCommandHandler(
            _relationshipRepository,
            _userBlockRepository,
            _logger);
    }

    [Fact]
    public async Task Handle_ValidRequest_CreatesNewRelationship()
    {
        // Arrange
        var requesterId = Guid.NewGuid();
        var addresseeId = Guid.NewGuid();
        var command = new SendFriendRequestCommand(requesterId, addresseeId);

        _userBlockRepository.HasBlockBetweenAsync(requesterId, addresseeId).Returns(false);
        _relationshipRepository.GetByUsersAndTypeAsync(requesterId, addresseeId, RelationshipType.Friendship)
            .Returns((Relationship?)null);
        _relationshipRepository.GetByUsersAndTypeAsync(addresseeId, requesterId, RelationshipType.Friendship)
            .Returns((Relationship?)null);

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

        // Assert
        result.Should().NotBeNull();
        result.Status.Should().Be("Pending");
        result.RelationshipId.Should().NotBeEmpty();

        _relationshipRepository.Received(1).Add(Arg.Is<Relationship>(r =>
            r.RequesterId == requesterId &&
            r.AddresseeId == addresseeId &&
            r.Type == RelationshipType.Friendship));
    }

    [Fact]
    public async Task Handle_BlockExists_ThrowsException()
    {
        // Arrange
        var requesterId = Guid.NewGuid();
        var addresseeId = Guid.NewGuid();
        var command = new SendFriendRequestCommand(requesterId, addresseeId);

        _userBlockRepository.HasBlockBetweenAsync(requesterId, addresseeId).Returns(true);

        // Act
        var act = async () => await _handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<SocialDomainException>()
            .WithMessage("*blocked*");

        _relationshipRepository.DidNotReceive().Add(Arg.Any<Relationship>());
    }

    [Fact]
    public async Task Handle_AlreadyFriends_ThrowsException()
    {
        // Arrange
        var requesterId = Guid.NewGuid();
        var addresseeId = Guid.NewGuid();
        var command = new SendFriendRequestCommand(requesterId, addresseeId);

        var existingRelationship = new Relationship(requesterId, addresseeId, RelationshipType.Friendship);
        existingRelationship.Accept();

        _userBlockRepository.HasBlockBetweenAsync(requesterId, addresseeId).Returns(false);
        _relationshipRepository.GetByUsersAndTypeAsync(requesterId, addresseeId, RelationshipType.Friendship)
            .Returns(existingRelationship);

        // Act
        var act = async () => await _handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<SocialDomainException>()
            .WithMessage("*already friends*");
    }

    [Fact]
    public async Task Handle_RequestAlreadyPending_ThrowsException()
    {
        // Arrange
        var requesterId = Guid.NewGuid();
        var addresseeId = Guid.NewGuid();
        var command = new SendFriendRequestCommand(requesterId, addresseeId);

        var existingRelationship = new Relationship(requesterId, addresseeId, RelationshipType.Friendship);
        // Status is Pending by default for Friendship

        _userBlockRepository.HasBlockBetweenAsync(requesterId, addresseeId).Returns(false);
        _relationshipRepository.GetByUsersAndTypeAsync(requesterId, addresseeId, RelationshipType.Friendship)
            .Returns(existingRelationship);

        // Act
        var act = async () => await _handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<SocialDomainException>()
            .WithMessage("*already pending*");
    }

    [Fact]
    public async Task Handle_MutualRequest_AutoAccepts()
    {
        // Arrange
        var requesterId = Guid.NewGuid();
        var addresseeId = Guid.NewGuid();
        var command = new SendFriendRequestCommand(requesterId, addresseeId);

        // EN: Other user already sent a pending request
        // VI: User kia đã gửi request đang chờ
        var reverseRelationship = new Relationship(addresseeId, requesterId, RelationshipType.Friendship);
        // Status is Pending by default

        _userBlockRepository.HasBlockBetweenAsync(requesterId, addresseeId).Returns(false);
        _relationshipRepository.GetByUsersAndTypeAsync(requesterId, addresseeId, RelationshipType.Friendship)
            .Returns((Relationship?)null);
        _relationshipRepository.GetByUsersAndTypeAsync(addresseeId, requesterId, RelationshipType.Friendship)
            .Returns(reverseRelationship);

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

        // Assert
        result.Should().NotBeNull();
        result.Status.Should().Be("Accepted");
        reverseRelationship.Status.Should().Be(RelationshipStatus.Accepted);

        _relationshipRepository.Received(1).Update(reverseRelationship);
        _relationshipRepository.DidNotReceive().Add(Arg.Any<Relationship>());
    }
}
