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 BlockUserCommandHandler.
/// VI: Unit tests cho BlockUserCommandHandler.
/// </summary>
public class BlockUserCommandHandlerTests
{
    private readonly IUserBlockRepository _userBlockRepository;
    private readonly IRelationshipRepository _relationshipRepository;
    private readonly ILogger<BlockUserCommandHandler> _logger;
    private readonly BlockUserCommandHandler _handler;

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

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

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

    [Fact]
    public async Task Handle_ValidRequest_CreatesBlock()
    {
        // Arrange
        var blockerId = Guid.NewGuid();
        var blockedId = Guid.NewGuid();
        var command = new BlockUserCommand(blockerId, blockedId, "Spam");

        _userBlockRepository.GetByUsersAsync(blockerId, blockedId).Returns((UserBlock?)null);
        _relationshipRepository.GetByUsersAndTypeAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), Arg.Any<RelationshipType>())
            .Returns((Relationship?)null);

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

        // Assert
        result.Should().NotBeNull();
        result.Success.Should().BeTrue();
        result.BlockId.Should().NotBeEmpty();

        _userBlockRepository.Received(1).Add(Arg.Is<UserBlock>(b =>
            b.BlockerId == blockerId &&
            b.BlockedId == blockedId &&
            b.Reason == "Spam"));
    }

    [Fact]
    public async Task Handle_SelfBlock_ThrowsException()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var command = new BlockUserCommand(userId, userId);

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

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

        _userBlockRepository.DidNotReceive().Add(Arg.Any<UserBlock>());
    }

    [Fact]
    public async Task Handle_AlreadyBlocked_ThrowsException()
    {
        // Arrange
        var blockerId = Guid.NewGuid();
        var blockedId = Guid.NewGuid();
        var command = new BlockUserCommand(blockerId, blockedId);

        var existingBlock = new UserBlock(blockerId, blockedId);
        _userBlockRepository.GetByUsersAsync(blockerId, blockedId).Returns(existingBlock);

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

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

    [Fact]
    public async Task Handle_WithExistingFriendship_RemovesFriendship()
    {
        // Arrange
        var blockerId = Guid.NewGuid();
        var blockedId = Guid.NewGuid();
        var command = new BlockUserCommand(blockerId, blockedId);

        var friendship = new Relationship(blockerId, blockedId, RelationshipType.Friendship);
        friendship.Accept(); // Make it accepted so it can be removed

        _userBlockRepository.GetByUsersAsync(blockerId, blockedId).Returns((UserBlock?)null);
        _relationshipRepository.GetByUsersAndTypeAsync(blockerId, blockedId, RelationshipType.Friendship)
            .Returns(friendship);
        _relationshipRepository.GetByUsersAndTypeAsync(blockedId, blockerId, RelationshipType.Friendship)
            .Returns((Relationship?)null);
        _relationshipRepository.GetByUsersAndTypeAsync(blockerId, blockedId, RelationshipType.Following)
            .Returns((Relationship?)null);
        _relationshipRepository.GetByUsersAndTypeAsync(blockedId, blockerId, RelationshipType.Following)
            .Returns((Relationship?)null);

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

        // Assert
        result.Success.Should().BeTrue();
        friendship.Status.Should().Be(RelationshipStatus.Cancelled);
        _relationshipRepository.Received(1).Update(friendship);
    }

    [Fact]
    public async Task Handle_WithNoReason_CreatesBlockWithoutReason()
    {
        // Arrange
        var blockerId = Guid.NewGuid();
        var blockedId = Guid.NewGuid();
        var command = new BlockUserCommand(blockerId, blockedId); // No reason

        _userBlockRepository.GetByUsersAsync(blockerId, blockedId).Returns((UserBlock?)null);
        _relationshipRepository.GetByUsersAndTypeAsync(Arg.Any<Guid>(), Arg.Any<Guid>(), Arg.Any<RelationshipType>())
            .Returns((Relationship?)null);

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

        // Assert
        result.Success.Should().BeTrue();
        _userBlockRepository.Received(1).Add(Arg.Is<UserBlock>(b => b.Reason == null));
    }
}
