using SIGCM2.Domain.Fiscal;

namespace SIGCM2.Domain.Entities;

/// <summary>
/// Entrada de Ingresos Brutos por provincia con versionado append-only.
/// Alicuota es INMUTABLE post-creación; cambiar el valor requiere crear una nueva versión
/// vía <see cref="NuevaVersion"/>.
/// </summary>
public sealed class IngresosBrutos
{
    public int Id { get; }
    public ProvinciaArgentina Provincia { get; }   // INMUTABLE
    public string Descripcion { get; }
    public decimal Alicuota { get; }               // INMUTABLE — usar NuevaVersion para cambiar
    public bool Activo { get; }
    public DateOnly VigenciaDesde { get; }
    public DateOnly? VigenciaHasta { get; }
    public int? PredecesorId { get; }
    public DateTime FechaCreacion { get; }
    public DateTime? FechaModificacion { get; }

    private IngresosBrutos(
        int id,
        ProvinciaArgentina provincia,
        string descripcion,
        decimal alicuota,
        bool activo,
        DateOnly vigenciaDesde,
        DateOnly? vigenciaHasta,
        int? predecesorId,
        DateTime fechaCreacion,
        DateTime? fechaModificacion)
    {
        Id = id;
        Provincia = provincia;
        Descripcion = descripcion;
        Alicuota = alicuota;
        Activo = activo;
        VigenciaDesde = vigenciaDesde;
        VigenciaHasta = vigenciaHasta;
        PredecesorId = predecesorId;
        FechaCreacion = fechaCreacion;
        FechaModificacion = fechaModificacion;
    }

    /// <summary>
    /// Factory para crear una nueva entrada de IIBB (Id=0 — BD asigna via IDENTITY; Activo=true).
    /// </summary>
    /// <exception cref="ArgumentException">Si Alicuota fuera de rango 0-100.</exception>
    public static IngresosBrutos ForCreation(
        ProvinciaArgentina provincia,
        string descripcion,
        decimal alicuota,
        DateOnly vigenciaDesde,
        DateOnly? vigenciaHasta = null,
        int? predecesorId = null)
    {
        ValidateAlicuota(alicuota, nameof(alicuota));
        ValidateVigencias(vigenciaDesde, vigenciaHasta);

        return new(
            id: 0,
            provincia: provincia,
            descripcion: descripcion,
            alicuota: alicuota,
            activo: true,
            vigenciaDesde: vigenciaDesde,
            vigenciaHasta: vigenciaHasta,
            predecesorId: predecesorId,
            fechaCreacion: default,
            fechaModificacion: null);
    }

    /// <summary>
    /// Factory para reconstruir desde repositorio (Dapper). No aplica validaciones de dominio.
    /// </summary>
    public static IngresosBrutos FromDb(
        int id,
        ProvinciaArgentina provincia,
        string descripcion,
        decimal alicuota,
        bool activo,
        DateOnly vigenciaDesde,
        DateOnly? vigenciaHasta,
        int? predecesorId,
        DateTime fechaCreacion,
        DateTime? fechaModificacion)
        => new(id, provincia, descripcion, alicuota, activo,
               vigenciaDesde, vigenciaHasta, predecesorId, fechaCreacion, fechaModificacion);

    /// <summary>
    /// Crea una nueva versión con la alícuota actualizada.
    /// Retorna la predecesora cerrada y la nueva versión.
    /// </summary>
    /// <exception cref="InvalidOperationException">Si la predecesora ya está cerrada (VigenciaHasta != null).</exception>
    /// <exception cref="ArgumentException">Si vigenciaDesde no es posterior a la predecesora, o nuevaAlicuota fuera de rango.</exception>
    /// <summary>
    /// <param name="now">Timestamp UTC provisto por el caller (Application layer via TimeProvider).</param>
    /// </summary>
    public (IngresosBrutos predecesoraCerrada, IngresosBrutos nuevaVersion) NuevaVersion(
        decimal nuevaAlicuota,
        DateOnly vigenciaDesde,
        DateTime now)
    {
        if (VigenciaHasta is not null)
            throw new InvalidOperationException(
                $"La versión {Id} ya está cerrada (VigenciaHasta={VigenciaHasta}). No puede generar nueva versión.");

        if (vigenciaDesde <= VigenciaDesde)
            throw new ArgumentException(
                $"vigenciaDesde ({vigenciaDesde}) debe ser posterior a VigenciaDesde de la predecesora ({VigenciaDesde}).",
                nameof(vigenciaDesde));

        ValidateAlicuota(nuevaAlicuota, nameof(nuevaAlicuota));

        var cerrada = new IngresosBrutos(
            id: Id,
            provincia: Provincia,
            descripcion: Descripcion,
            alicuota: Alicuota,
            activo: Activo,
            vigenciaDesde: VigenciaDesde,
            vigenciaHasta: vigenciaDesde.AddDays(-1),
            predecesorId: PredecesorId,
            fechaCreacion: FechaCreacion,
            fechaModificacion: now);

        var nueva = ForCreation(
            provincia: Provincia,
            descripcion: Descripcion,
            alicuota: nuevaAlicuota,
            vigenciaDesde: vigenciaDesde,
            vigenciaHasta: null,
            predecesorId: Id);

        return (cerrada, nueva);
    }

    // ── Cosmetic mutators (NO WithAlicuota, NO WithProvincia) ─────────────────

    /// <summary>Actualiza la descripción. Alicuota y Provincia permanecen inmutables.</summary>
    public IngresosBrutos WithDescripcion(string descripcion, DateTime now)
        => new(Id, Provincia, descripcion, Alicuota, Activo,
               VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, now);

    /// <summary>Retorna instancia con Activo=false.</summary>
    public IngresosBrutos Deactivate(DateTime now)
        => new(Id, Provincia, Descripcion, Alicuota, false,
               VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, now);

    /// <summary>Retorna instancia con Activo=true.</summary>
    public IngresosBrutos Reactivate(DateTime now)
        => new(Id, Provincia, Descripcion, Alicuota, true,
               VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, now);

    /// <summary>
    /// Cierra la vigencia seteando VigenciaHasta. Usado por el handler de NuevaVersion.
    /// </summary>
    public IngresosBrutos CerrarVigencia(DateOnly vigenciaHasta, DateTime now)
        => new(Id, Provincia, Descripcion, Alicuota, Activo,
               VigenciaDesde, vigenciaHasta, PredecesorId, FechaCreacion, now);

    // ── Private helpers ───────────────────────────────────────────────────────

    private static void ValidateAlicuota(decimal alicuota, string paramName)
    {
        if (alicuota < 0m || alicuota > 100m)
            throw new ArgumentException(
                $"Alicuota ({alicuota}) debe estar entre 0 y 100.",
                paramName);
    }

    private static void ValidateVigencias(DateOnly desde, DateOnly? hasta)
    {
        if (hasta.HasValue && hasta.Value < desde)
            throw new ArgumentException(
                $"VigenciaHasta ({hasta}) no puede ser anterior a VigenciaDesde ({desde}).",
                "vigenciaHasta");
    }
}
