using FluentAssertions;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Rubros.GetById;
using SIGCM2.Domain.Entities;
using SIGCM2.Domain.Exceptions;

namespace SIGCM2.Application.Tests.Rubros.GetById;

public class GetRubroByIdQueryHandlerTests
{
    private static readonly FakeTimeProvider FakeTime =
        new(new DateTimeOffset(2026, 4, 18, 12, 0, 0, TimeSpan.Zero));

    private readonly IRubroRepository _repo = Substitute.For<IRubroRepository>();

    private static DateTime UtcNow => FakeTime.GetUtcNow().UtcDateTime;

    private static Rubro MakeRubro(
        int id,
        int? parentId = null,
        bool activo = true,
        int? tarifarioBaseId = null,
        DateTime? fechaModificacion = null)
        => new(id, parentId, $"Rubro{id}", orden: 1, activo, tarifarioBaseId,
               fechaCreacion: UtcNow, fechaModificacion);

    // ── Handle_RubroExiste_ActivoPorDefecto_RetornaDto ───────────────────────

    [Fact]
    public async Task Handle_RubroExiste_ActivoPorDefecto_RetornaDto()
    {
        var rubro = MakeRubro(id: 5, parentId: 2, tarifarioBaseId: 10);
        _repo.GetByIdAsync(5, Arg.Any<CancellationToken>()).Returns(rubro);

        var handler = new GetRubroByIdQueryHandler(_repo);
        var result = await handler.Handle(new GetRubroByIdQuery(Id: 5));

        result.Should().NotBeNull();
        result.Id.Should().Be(5);
        result.Nombre.Should().Be("Rubro5");
        result.ParentId.Should().Be(2);
        result.Orden.Should().Be(1);
        result.Activo.Should().BeTrue();
        result.TarifarioBaseId.Should().Be(10);
        result.FechaCreacion.Should().Be(UtcNow);
        result.FechaModificacion.Should().BeNull();
    }

    // ── Handle_RubroNoExiste_LanzaRubroNotFoundException ────────────────────

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

        var handler = new GetRubroByIdQueryHandler(_repo);
        var act = () => handler.Handle(new GetRubroByIdQuery(Id: 99));

        await act.Should().ThrowAsync<RubroNotFoundException>()
            .WithMessage("*99*");
    }

    // ── Handle_RubroNotFoundException_ContienIdCorrecto ─────────────────────

    [Fact]
    public async Task Handle_RubroNotFoundException_ContieneIdCorrecto()
    {
        _repo.GetByIdAsync(42, Arg.Any<CancellationToken>()).Returns((Rubro?)null);

        var handler = new GetRubroByIdQueryHandler(_repo);

        var ex = await Assert.ThrowsAsync<RubroNotFoundException>(
            () => handler.Handle(new GetRubroByIdQuery(Id: 42)));

        ex.Id.Should().Be(42);
    }

    // ── Handle_CancellationToken_SePropagaAlRepo ─────────────────────────────

    [Fact]
    public async Task Handle_CancellationToken_SePropagaAlRepo()
    {
        // The handler signature accepts CancellationToken and forwards it to the repo.
        // We verify the repo is called exactly once with the matching id and any token.
        _repo.GetByIdAsync(7, Arg.Any<CancellationToken>()).Returns(MakeRubro(7));

        var handler = new GetRubroByIdQueryHandler(_repo);
        await handler.Handle(new GetRubroByIdQuery(Id: 7));

        await _repo.Received(1).GetByIdAsync(7, Arg.Any<CancellationToken>());
    }

    // ── Handle_FechaCreacion_SeSerializaComoInstantUTC ───────────────────────

    [Fact]
    public async Task Handle_FechaCreacion_SeSerializaComoInstantUTC()
    {
        var expectedUtc = new DateTime(2026, 1, 15, 8, 30, 0, DateTimeKind.Utc);
        var rubro = new Rubro(3, null, "RubroUTC", 0, true, null, expectedUtc, null);
        _repo.GetByIdAsync(3, Arg.Any<CancellationToken>()).Returns(rubro);

        var handler = new GetRubroByIdQueryHandler(_repo);
        var result = await handler.Handle(new GetRubroByIdQuery(Id: 3));

        result.FechaCreacion.Should().Be(expectedUtc);
        result.FechaCreacion.Kind.Should().Be(DateTimeKind.Utc);
    }

    // ── Handle_FechaModificacion_Null_SeRetornaNull ──────────────────────────

    [Fact]
    public async Task Handle_FechaModificacion_Null_SeRetornaNull()
    {
        var rubro = MakeRubro(id: 8, fechaModificacion: null);
        _repo.GetByIdAsync(8, Arg.Any<CancellationToken>()).Returns(rubro);

        var handler = new GetRubroByIdQueryHandler(_repo);
        var result = await handler.Handle(new GetRubroByIdQuery(Id: 8));

        result.FechaModificacion.Should().BeNull();
    }

    // ── Handle_FechaModificacion_ConValor_SeRetornaCorrecto ──────────────────

    [Fact]
    public async Task Handle_FechaModificacion_ConValor_SeRetornaCorrecto()
    {
        var fechaMod = new DateTime(2026, 3, 10, 10, 0, 0, DateTimeKind.Utc);
        var rubro = MakeRubro(id: 9, fechaModificacion: fechaMod);
        _repo.GetByIdAsync(9, Arg.Any<CancellationToken>()).Returns(rubro);

        var handler = new GetRubroByIdQueryHandler(_repo);
        var result = await handler.Handle(new GetRubroByIdQuery(Id: 9));

        result.FechaModificacion.Should().Be(fechaMod);
    }

    // ── Handle_RubroRaiz_SinParent_RetornaDtoConParentIdNull ─────────────────

    [Fact]
    public async Task Handle_RubroRaiz_SinParent_RetornaDtoConParentIdNull()
    {
        var rubro = MakeRubro(id: 1, parentId: null);
        _repo.GetByIdAsync(1, Arg.Any<CancellationToken>()).Returns(rubro);

        var handler = new GetRubroByIdQueryHandler(_repo);
        var result = await handler.Handle(new GetRubroByIdQuery(Id: 1));

        result.ParentId.Should().BeNull();
    }

    // ── Handle_RubroConTarifarioBaseId_SeRetornaCorrecto ────────────────────

    [Fact]
    public async Task Handle_RubroConTarifarioBaseId_SeRetornaCorrecto()
    {
        var rubro = MakeRubro(id: 11, tarifarioBaseId: 42);
        _repo.GetByIdAsync(11, Arg.Any<CancellationToken>()).Returns(rubro);

        var handler = new GetRubroByIdQueryHandler(_repo);
        var result = await handler.Handle(new GetRubroByIdQuery(Id: 11));

        result.TarifarioBaseId.Should().Be(42);
    }
}
