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

namespace SIGCM2.Application.Tests.TiposDeIva.Update;

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

    private static TipoDeIva MakeEntity(int id = 1) => TipoDeIva.FromDb(
        id: id,
        codigo: "IVA_21",
        descripcion: "IVA 21%",
        porcentaje: 21m,
        aplicaIVA: true,
        activo: true,
        vigenciaDesde: new DateOnly(2024, 1, 1),
        vigenciaHasta: null,
        predecesorId: null,
        fechaCreacion: DateTime.UtcNow,
        fechaModificacion: null);

    private static UpdateTipoDeIvaCommand ValidCommand(int id = 1) => new(
        Id: id,
        Codigo: "IVA_21",
        Descripcion: "IVA 21% actualizado",
        AplicaIVA: true,
        Activo: true);

    public UpdateTipoDeIvaCommandHandlerTests()
    {
        _handler = new UpdateTipoDeIvaCommandHandler(_repo, _audit, TimeProvider.System);
        _repo.GetByIdAsync(1, Arg.Any<CancellationToken>()).Returns(MakeEntity());
        _repo.UpdateCosmeticoAsync(Arg.Any<int>(), Arg.Any<string>(), Arg.Any<string>(),
                                   Arg.Any<bool>(), Arg.Any<bool>(), Arg.Any<CancellationToken>())
             .Returns(true);
    }

    // ── not found ────────────────────────────────────────────────────────────

    [Fact]
    public async Task Handle_NotFound_ThrowsTipoDeIvaNotFoundException()
    {
        _repo.GetByIdAsync(99, Arg.Any<CancellationToken>()).Returns((TipoDeIva?)null);

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

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

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

        Assert.Equal("IVA 21% actualizado", result.Descripcion);
    }

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

        await _repo.Received(1).UpdateCosmeticoAsync(
            1, "IVA_21", "IVA 21% actualizado", true, true,
            Arg.Any<CancellationToken>());
    }

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

        await _audit.Received(1).LogAsync(
            action: "tipo_iva.update",
            targetType: "TipoDeIva",
            targetId: "1",
            metadata: Arg.Any<object?>(),
            ct: 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()));
    }

    // ── triangulation: activo toggle ─────────────────────────────────────────

    [Fact]
    public async Task Handle_DeactivateViaUpdate_ReturnsDtoWithActivoFalse()
    {
        var cmd = new UpdateTipoDeIvaCommand(Id: 1, Codigo: "IVA_21",
            Descripcion: "IVA 21%", AplicaIVA: true, Activo: false);

        var result = await _handler.Handle(cmd);

        // The handler passes the Activo value to UpdateCosmeticoAsync
        await _repo.Received(1).UpdateCosmeticoAsync(
            1, "IVA_21", "IVA 21%", true, false,
            Arg.Any<CancellationToken>());
        Assert.False(result.Activo);
    }
}
