using FluentAssertions;
using MiningService.API.Application.Commands;
using MiningService.Domain.AggregatesModel.MinerAggregate;
using MiningService.Domain.Exceptions;
using NSubstitute;
using Xunit;

namespace MiningService.UnitTests.Application.Commands;

/// <summary>
/// EN: Unit tests for Admin Command Handlers.
/// VI: Unit tests cho Admin Command Handlers.
/// </summary>
public class AdminCommandHandlerTests
{
    private readonly IMinerRepository _minerRepository;

    public AdminCommandHandlerTests()
    {
        _minerRepository = Substitute.For<IMinerRepository>();
    }

    #region BanMinerCommandHandler Tests

    [Fact]
    public async Task BanMiner_ExistingMiner_BansSuccessfully()
    {
        // Arrange
        var minerId = Guid.NewGuid();
        var miner = Miner.Create(Guid.NewGuid());
        var command = new BanMinerCommand(minerId, "Violation");
        var handler = new BanMinerCommandHandler(_minerRepository);

        _minerRepository.GetByIdAsync(minerId, Arg.Any<CancellationToken>())
            .Returns(miner);
        _minerRepository.UnitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>())
            .Returns(true);

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

        // Assert
        result.Should().NotBeNull();
        result.Success.Should().BeTrue();
        miner.Status.Should().Be(MinerStatus.Suspended);
    }

    [Fact]
    public async Task BanMiner_MinerNotFound_ThrowsException()
    {
        // Arrange
        var minerId = Guid.NewGuid();
        var command = new BanMinerCommand(minerId, "Violation");
        var handler = new BanMinerCommandHandler(_minerRepository);

        _minerRepository.GetByIdAsync(minerId, Arg.Any<CancellationToken>())
            .Returns((Miner?)null);

        // Act & Assert
        await Assert.ThrowsAsync<MinerNotFoundException>(() =>
            handler.Handle(command, CancellationToken.None));
    }

    #endregion

    #region AdjustMinerPointsCommandHandler Tests

    [Fact]
    public async Task AdjustPoints_PositiveAmount_AddsPoints()
    {
        // Arrange
        var minerId = Guid.NewGuid();
        var miner = Miner.Create(Guid.NewGuid());
        var command = new AdjustMinerPointsCommand(minerId, 100m, "Admin bonus");
        var handler = new AdjustMinerPointsCommandHandler(_minerRepository);

        _minerRepository.GetByIdAsync(minerId, Arg.Any<CancellationToken>())
            .Returns(miner);
        _minerRepository.UnitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>())
            .Returns(true);

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

        // Assert
        result.Should().NotBeNull();
        result.Success.Should().BeTrue();
        result.NewBalance.Should().Be(100m);
    }

    [Fact]
    public async Task AdjustPoints_MinerNotFound_ThrowsException()
    {
        // Arrange
        var minerId = Guid.NewGuid();
        var command = new AdjustMinerPointsCommand(minerId, 100m, "Bonus");
        var handler = new AdjustMinerPointsCommandHandler(_minerRepository);

        _minerRepository.GetByIdAsync(minerId, Arg.Any<CancellationToken>())
            .Returns((Miner?)null);

        // Act & Assert
        await Assert.ThrowsAsync<MinerNotFoundException>(() =>
            handler.Handle(command, CancellationToken.None));
    }

    #endregion

    #region ResetMinerStreakCommandHandler Tests

    [Fact]
    public async Task ResetStreak_ValidMiner_ResetsSuccessfully()
    {
        // Arrange
        var minerId = Guid.NewGuid();
        var miner = Miner.Create(Guid.NewGuid());
        var command = new ResetMinerStreakCommand(minerId, "Admin reset");
        var handler = new ResetMinerStreakCommandHandler(_minerRepository);

        _minerRepository.GetByIdAsync(minerId, Arg.Any<CancellationToken>())
            .Returns(miner);
        _minerRepository.UnitOfWork.SaveEntitiesAsync(Arg.Any<CancellationToken>())
            .Returns(true);

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

        // Assert
        result.Should().NotBeNull();
        result.Success.Should().BeTrue();
    }

    #endregion
}
