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

namespace SIGCM2.Application.Tests.Medios.Create;

public class CreateMedioCommandHandlerTests
{
    private readonly IMedioRepository _repo = Substitute.For<IMedioRepository>();
    private readonly IAuditLogger _audit = Substitute.For<IAuditLogger>();
    private readonly CreateMedioCommandHandler _handler;

    private static CreateMedioCommand ValidCommand() => new(
        Codigo: "DIARIO_LA_VOZ",
        Nombre: "Diario La Voz",
        Tipo: TipoMedio.Diario,
        PlataformaEmpresaId: null);

    public CreateMedioCommandHandlerTests()
    {
        _handler = new CreateMedioCommandHandler(_repo, _audit);
        _repo.ExistsByCodigoAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(false);
        _repo.AddAsync(Arg.Any<Medio>(), Arg.Any<CancellationToken>()).Returns(1);
    }

    // ── duplicate → throws ───────────────────────────────────────────────────

    [Fact]
    public async Task Handle_DuplicateCodigo_ThrowsMedioCodigoDuplicadoException()
    {
        _repo.ExistsByCodigoAsync("DIARIO_LA_VOZ", Arg.Any<CancellationToken>()).Returns(true);

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

    [Fact]
    public async Task Handle_DuplicateCodigo_DoesNotCallAddAsync()
    {
        _repo.ExistsByCodigoAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(true);

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

        await _repo.DidNotReceive().AddAsync(Arg.Any<Medio>(), Arg.Any<CancellationToken>());
    }

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

    [Fact]
    public async Task Handle_HappyPath_ReturnsDtoWithIdFromRepository()
    {
        _repo.AddAsync(Arg.Any<Medio>(), Arg.Any<CancellationToken>()).Returns(42);

        var result = await _handler.Handle(ValidCommand());

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

    [Fact]
    public async Task Handle_HappyPath_DtoContainsCorrectFields()
    {
        _repo.AddAsync(Arg.Any<Medio>(), Arg.Any<CancellationToken>()).Returns(5);

        var result = await _handler.Handle(ValidCommand());

        Assert.Equal("DIARIO_LA_VOZ", result.Codigo);
        Assert.Equal("Diario La Voz", result.Nombre);
        Assert.Equal(TipoMedio.Diario, result.Tipo);
        Assert.Null(result.PlataformaEmpresaId);
        Assert.True(result.Activo);
    }

    [Fact]
    public async Task Handle_HappyPath_NormalizesCodigoToUppercase()
    {
        var cmd = new CreateMedioCommand("diario_la_voz", "Diario La Voz", TipoMedio.Diario, null);

        await _handler.Handle(cmd);

        await _repo.Received(1).AddAsync(
            Arg.Is<Medio>(m => m.Codigo == "DIARIO_LA_VOZ"),
            Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_HappyPath_CallsAuditLogWithCreateAction()
    {
        _repo.AddAsync(Arg.Any<Medio>(), Arg.Any<CancellationToken>()).Returns(7);

        await _handler.Handle(ValidCommand());

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

    [Fact]
    public async Task Handle_HappyPath_ChecksNormalizedCodigoForDuplicate()
    {
        var cmd = new CreateMedioCommand("diario_la_voz", "Diario La Voz", TipoMedio.Diario, null);

        await _handler.Handle(cmd);

        // ExistsByCodigoAsync should be called with the uppercased version
        await _repo.Received(1).ExistsByCodigoAsync("DIARIO_LA_VOZ", Arg.Any<CancellationToken>());
    }
}
