using System.Reflection;
using FluentAssertions;
using SIGCM2.Domain.Entities;
using SIGCM2.Domain.Exceptions;

namespace SIGCM2.Application.Tests.Domain.Fiscal;

public class TipoDeIvaTests
{
    private static readonly DateOnly Desde2020 = new(2020, 1, 1);
    private static readonly DateOnly Desde2026 = new(2026, 6, 1);

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

    // ── T200.10: ForCreation validations (RED) ────────────────────────────────

    [Fact]
    public void ForCreation_ValidArgs_ReturnsEntity()
    {
        var iva = TipoDeIva.ForCreation("IVA_21", "IVA 21%", 21m, true, Desde2020);

        iva.Codigo.Should().Be("IVA_21");
        iva.Descripcion.Should().Be("IVA 21%");
        iva.Porcentaje.Should().Be(21m);
        iva.AplicaIVA.Should().BeTrue();
        iva.Activo.Should().BeTrue();
        iva.Id.Should().Be(0);
        iva.PredecesorId.Should().BeNull();
        iva.VigenciaHasta.Should().BeNull();
    }

    [Theory]
    [InlineData("INVALIDO")]
    [InlineData("IVA21")]
    [InlineData("iva_21")]
    [InlineData("IVA-21")]
    [InlineData("")]
    [InlineData("   ")]
    public void ForCreation_CodigoInvalido_ThrowsArgumentException(string codigoInvalido)
    {
        var act = () => TipoDeIva.ForCreation(codigoInvalido, "desc", 21m, true, Desde2020);

        act.Should().Throw<ArgumentException>()
            .WithParameterName("codigo");
    }

    [Theory]
    [InlineData("EXENTO")]
    [InlineData("NO_GRAVADO")]
    [InlineData("IVA_21")]
    [InlineData("IVA_105")]
    [InlineData("IVA_0")]
    public void ForCreation_CodigoValido_NoLanza(string codigoValido)
    {
        var act = () => TipoDeIva.ForCreation(codigoValido, "desc", 0m, false, Desde2020);

        act.Should().NotThrow();
    }

    [Fact]
    public void ForCreation_PorcentajeNegativo_ThrowsArgumentException()
    {
        var act = () => TipoDeIva.ForCreation("IVA_21", "desc", -5m, true, Desde2020);

        act.Should().Throw<ArgumentException>()
            .WithParameterName("porcentaje");
    }

    [Fact]
    public void ForCreation_PorcentajeMayorA100_ThrowsArgumentException()
    {
        var act = () => TipoDeIva.ForCreation("IVA_21", "desc", 150m, true, Desde2020);

        act.Should().Throw<ArgumentException>()
            .WithParameterName("porcentaje");
    }

    [Theory]
    [InlineData(0)]
    [InlineData(10.5)]
    [InlineData(21)]
    [InlineData(100)]
    public void ForCreation_PorcentajeEnRango_NoLanza(double porcentaje)
    {
        var act = () => TipoDeIva.ForCreation("IVA_21", "desc", (decimal)porcentaje, true, Desde2020);

        act.Should().NotThrow();
    }

    [Fact]
    public void ForCreation_VigenciaHastaMenorQueDesde_ThrowsArgumentException()
    {
        var desde = new DateOnly(2026, 6, 1);
        var hasta = new DateOnly(2026, 1, 1); // antes que desde

        var act = () => TipoDeIva.ForCreation("IVA_21", "desc", 21m, true, desde, hasta);

        act.Should().Throw<ArgumentException>()
            .WithParameterName("vigenciaHasta");
    }

    [Fact]
    public void ForCreation_VigenciaHastaIgualQueDesde_NoLanza()
    {
        var fecha = new DateOnly(2026, 1, 1);
        var act = () => TipoDeIva.ForCreation("IVA_21", "desc", 21m, true, fecha, fecha);

        act.Should().NotThrow();
    }

    // ── T200.12: ForCreation sets all properties correctly ────────────────────

    [Fact]
    public void ForCreation_SetsAllProps_Correctly()
    {
        var iva = TipoDeIva.ForCreation("EXENTO", "Exento de IVA", 0m, false, Desde2020);

        iva.Id.Should().Be(0);
        iva.Activo.Should().BeTrue();
        iva.VigenciaHasta.Should().BeNull();
        iva.VigenciaDesde.Should().Be(Desde2020);
        iva.PredecesorId.Should().BeNull();
    }

    // ── T200.11: With* methods ────────────────────────────────────────────────

    [Fact]
    public void WithDescripcion_ReturnsNewInstanceWithUpdatedDescripcion()
    {
        var original = MakeTipoDeIva(descripcion: "Original");

        var updated = original.WithDescripcion("Nueva descripcion", DateTime.UtcNow);

        updated.Should().NotBeSameAs(original);
        updated.Descripcion.Should().Be("Nueva descripcion");
        updated.Porcentaje.Should().Be(original.Porcentaje, "Porcentaje es inmutable");
    }

    [Fact]
    public void WithCodigo_ReturnsNewInstanceWithUpdatedCodigo()
    {
        var original = MakeTipoDeIva(codigo: "IVA_21");

        var updated = original.WithCodigo("NO_GRAVADO", DateTime.UtcNow);

        updated.Codigo.Should().Be("NO_GRAVADO");
        updated.Porcentaje.Should().Be(original.Porcentaje);
        updated.Id.Should().Be(original.Id);
    }

    [Fact]
    public void WithAplicaIVA_ReturnsNewInstanceWithUpdatedAplicaIVA()
    {
        var original = MakeTipoDeIva(aplicaIVA: true);

        var updated = original.WithAplicaIVA(false, DateTime.UtcNow);

        updated.AplicaIVA.Should().BeFalse();
        updated.Porcentaje.Should().Be(original.Porcentaje);
    }

    [Fact]
    public void Deactivate_ReturnsNewInstanceWithActivoFalse()
    {
        var original = MakeTipoDeIva(activo: true);

        var deactivated = original.Deactivate(DateTime.UtcNow);

        deactivated.Activo.Should().BeFalse();
        deactivated.Porcentaje.Should().Be(original.Porcentaje);
        deactivated.Id.Should().Be(original.Id);
    }

    [Fact]
    public void Reactivate_ReturnsNewInstanceWithActivoTrue()
    {
        var original = MakeTipoDeIva(activo: false);

        var reactivated = original.Reactivate(DateTime.UtcNow);

        reactivated.Activo.Should().BeTrue();
    }

    [Fact]
    public void CerrarVigencia_SetsVigenciaHasta()
    {
        var original = MakeTipoDeIva(vigenciaHasta: null);
        var hasta = new DateOnly(2026, 5, 31);

        var cerrado = original.CerrarVigencia(hasta, DateTime.UtcNow);

        cerrado.VigenciaHasta.Should().Be(hasta);
        cerrado.Porcentaje.Should().Be(original.Porcentaje);
        cerrado.Id.Should().Be(original.Id);
    }

    // ── T200.13 & T200.15: NuevaVersion tuple (RED then GREEN) ───────────────

    [Fact]
    public void NuevaVersion_ReturnsPredecesoraCerradaYNuevaVersion()
    {
        var predecesora = MakeTipoDeIva(id: 5, porcentaje: 21m, vigenciaDesde: Desde2020, vigenciaHasta: null);

        var (cerrada, nueva) = predecesora.NuevaVersion(23.5m, Desde2026, DateTime.UtcNow);

        cerrada.Id.Should().Be(5);
        cerrada.VigenciaHasta.Should().Be(Desde2026.AddDays(-1), "predecesora queda cerrada el día anterior");
        cerrada.Porcentaje.Should().Be(21m, "porcentaje predecesora no cambia");

        nueva.Id.Should().Be(0, "ID lo asigna la BD");
        nueva.PredecesorId.Should().Be(5);
        nueva.Porcentaje.Should().Be(23.5m);
        nueva.VigenciaDesde.Should().Be(Desde2026);
        nueva.VigenciaHasta.Should().BeNull("nueva versión nace abierta");
        nueva.Codigo.Should().Be(predecesora.Codigo, "hereda el código");
        nueva.Descripcion.Should().Be(predecesora.Descripcion, "hereda la descripción");
        nueva.Activo.Should().BeTrue();
    }

    [Fact]
    public void NuevaVersion_PorcentajeDistinto_EsIndependiente()
    {
        var predecesora = MakeTipoDeIva(porcentaje: 10.5m, vigenciaDesde: Desde2020);
        var nuevaVigencia = new DateOnly(2025, 1, 1);

        var (_, nueva) = predecesora.NuevaVersion(21m, nuevaVigencia, DateTime.UtcNow);

        nueva.Porcentaje.Should().Be(21m);
        predecesora.Porcentaje.Should().Be(10.5m, "predecesora no muta");
    }

    // ── T200.13: NuevaVersion validations ────────────────────────────────────

    [Fact]
    public void NuevaVersion_PredecesoraConVigenciaHasta_ThrowsInvalidOperationException()
    {
        var predecesora = MakeTipoDeIva(
            vigenciaDesde: Desde2020,
            vigenciaHasta: new DateOnly(2025, 12, 31)); // ya cerrada

        var act = () => predecesora.NuevaVersion(23.5m, Desde2026, DateTime.UtcNow);

        act.Should().Throw<InvalidOperationException>();
    }

    [Fact]
    public void NuevaVersion_VigenciaDesdeIgualAPredecesora_ThrowsArgumentException()
    {
        var predecesora = MakeTipoDeIva(vigenciaDesde: Desde2020, vigenciaHasta: null);

        var act = () => predecesora.NuevaVersion(23.5m, Desde2020, DateTime.UtcNow); // igual a VigenciaDesde predecesora

        act.Should().Throw<ArgumentException>()
            .WithParameterName("vigenciaDesde");
    }

    [Fact]
    public void NuevaVersion_VigenciaDesDeMenorQuePredecesora_ThrowsArgumentException()
    {
        var predecesora = MakeTipoDeIva(vigenciaDesde: Desde2026, vigenciaHasta: null);
        var vigenciaAnterior = new DateOnly(2020, 1, 1);

        var act = () => predecesora.NuevaVersion(23.5m, vigenciaAnterior, DateTime.UtcNow);

        act.Should().Throw<ArgumentException>()
            .WithParameterName("vigenciaDesde");
    }

    [Fact]
    public void NuevaVersion_NuevoPorcentajeNegativo_ThrowsArgumentException()
    {
        var predecesora = MakeTipoDeIva(vigenciaDesde: Desde2020, vigenciaHasta: null);

        var act = () => predecesora.NuevaVersion(-1m, Desde2026, DateTime.UtcNow);

        act.Should().Throw<ArgumentException>()
            .WithParameterName("nuevoPorcentaje");
    }

    [Fact]
    public void NuevaVersion_NuevoPorcentajeMayorA100_ThrowsArgumentException()
    {
        var predecesora = MakeTipoDeIva(vigenciaDesde: Desde2020, vigenciaHasta: null);

        var act = () => predecesora.NuevaVersion(101m, Desde2026, DateTime.UtcNow);

        act.Should().Throw<ArgumentException>()
            .WithParameterName("nuevoPorcentaje");
    }

    // ── T200.16: reflection — NO debe existir WithPorcentaje ─────────────────

    [Fact]
    public void TipoDeIva_No_Debe_Exponer_WithPorcentaje()
    {
        var method = typeof(TipoDeIva).GetMethod("WithPorcentaje", BindingFlags.Public | BindingFlags.Instance);

        method.Should().BeNull("Porcentaje es inmutable — usar NuevaVersion");
    }

    // ── Immutable fields preserved across With* ───────────────────────────────

    [Fact]
    public void WithDescripcion_PreservesImmutableFields()
    {
        var original = MakeTipoDeIva(id: 99, porcentaje: 21m, vigenciaDesde: Desde2020);

        var updated = original.WithDescripcion("Nueva", DateTime.UtcNow);

        updated.Id.Should().Be(99);
        updated.Porcentaje.Should().Be(21m);
        updated.VigenciaDesde.Should().Be(Desde2020);
    }

    [Fact]
    public void FromDb_SetsAllProperties()
    {
        var fechaCreacion = DateTime.UtcNow;
        var iva = TipoDeIva.FromDb(
            id: 7, codigo: "IVA_21", descripcion: "IVA 21%",
            porcentaje: 21m, aplicaIVA: true, activo: true,
            vigenciaDesde: Desde2020, vigenciaHasta: null,
            predecesorId: null, fechaCreacion: fechaCreacion, fechaModificacion: null);

        iva.Id.Should().Be(7);
        iva.Codigo.Should().Be("IVA_21");
        iva.Porcentaje.Should().Be(21m);
        iva.FechaCreacion.Should().Be(fechaCreacion);
    }
}
