using NSubstitute;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Audit;
using SIGCM2.Application.TiposDeIva.Create;
using SIGCM2.Domain.Entities;
using SIGCM2.Domain.Exceptions;

namespace SIGCM2.Application.Tests.TiposDeIva.Create;

public class CreateTipoDeIvaCommandHandlerTests
{
    private readonly ITipoDeIvaRepository _repo = Substitute.For<ITipoDeIvaRepository>();
    private readonly IAuditLogger _audit = Substitute.For<IAuditLogger>();
    private readonly CreateTipoDeIvaCommandHandler _handler;

    private static CreateTipoDeIvaCommand ValidCommand() => new(
        Codigo: "IVA_21",
        Descripcion: "IVA 21%",
        Porcentaje: 21m,
        AplicaIVA: true,
        VigenciaDesde: new DateOnly(2024, 1, 1));

    public CreateTipoDeIvaCommandHandlerTests()
    {
        _handler = new CreateTipoDeIvaCommandHandler(_repo, _audit, TimeProvider.System);
        _repo.InsertAsync(Arg.Any<TipoDeIva>(), Arg.Any<CancellationToken>()).Returns(42);
    }

    // ── happy path ───────────────────────────────────────────────────────────

    [Fact]
    public async Task Handle_HappyPath_ReturnsDtoWithIdFromRepository()
    {
        var result = await _handler.Handle(ValidCommand());

        Assert.Equal(42, result.Id);
    }

    [Fact]
    public async Task Handle_HappyPath_DtoContainsCorrectFields()
    {
        var result = await _handler.Handle(ValidCommand());

        Assert.Equal("IVA_21", result.Codigo);
        Assert.Equal("IVA 21%", result.Descripcion);
        Assert.Equal(21m, result.Porcentaje);
        Assert.True(result.AplicaIVA);
        Assert.True(result.Activo);
    }

    [Fact]
    public async Task Handle_HappyPath_CallsAuditWithCreateAction()
    {
        await _handler.Handle(ValidCommand());

        await _audit.Received(1).LogAsync(
            action: "tipo_iva.create",
            targetType: "TipoDeIva",
            targetId: "42",
            metadata: Arg.Any<object?>(),
            ct: Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_HappyPath_CallsInsertOnce()
    {
        await _handler.Handle(ValidCommand());

        await _repo.Received(1).InsertAsync(Arg.Any<TipoDeIva>(), Arg.Any<CancellationToken>());
    }

    // ── audit fail-closed ────────────────────────────────────────────────────

    [Fact]
    public async Task Handle_AuditLoggerThrows_ExceptionBubblesUp()
    {
        _audit.LogAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(),
                        Arg.Any<object?>(), Arg.Any<CancellationToken>())
              .Returns(Task.FromException(new InvalidOperationException("audit fail")));

        await Assert.ThrowsAsync<InvalidOperationException>(
            () => _handler.Handle(ValidCommand()));
    }

    [Fact]
    public async Task Handle_AuditLoggerThrows_InsertWasCalledButScopeNotCompleted()
    {
        _audit.LogAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(),
                        Arg.Any<object?>(), Arg.Any<CancellationToken>())
              .Returns(Task.FromException(new InvalidOperationException("audit fail")));

        try { await _handler.Handle(ValidCommand()); } catch (InvalidOperationException) { }

        // Insert was called (it's before audit in the scope), but audit threw = scope not completed
        await _repo.Received(1).InsertAsync(Arg.Any<TipoDeIva>(), Arg.Any<CancellationToken>());
        await _audit.Received(1).LogAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(),
                                          Arg.Any<object?>(), Arg.Any<CancellationToken>());
    }

    // ── triangulation: different porcentaje value ────────────────────────────

    [Fact]
    public async Task Handle_WithZeroPorcentaje_ReturnsDtoWithCorrectPorcentaje()
    {
        var cmd = new CreateTipoDeIvaCommand(
            Codigo: "EXENTO",
            Descripcion: "Exento de IVA",
            Porcentaje: 0m,
            AplicaIVA: false,
            VigenciaDesde: new DateOnly(2024, 1, 1));

        var result = await _handler.Handle(cmd);

        Assert.Equal(0m, result.Porcentaje);
        Assert.False(result.AplicaIVA);
    }
}
