using System.Text.RegularExpressions;
using SIGCM2.Domain.Exceptions;

namespace SIGCM2.Domain.Entities;

/// <summary>
/// Tipo de IVA de referencia con versionado append-only.
/// Porcentaje es INMUTABLE post-creación; cambiar el valor requiere crear una nueva versión
/// vía <see cref="NuevaVersion"/>.
/// </summary>
public sealed class TipoDeIva
{
    private static readonly Regex CodigoRegex =
        new(@"^(EXENTO|NO_GRAVADO|IVA_\d+)$", RegexOptions.Compiled | RegexOptions.CultureInvariant);

    public int Id { get; }
    public string Codigo { get; }
    public string Descripcion { get; }
    public decimal Porcentaje { get; }        // INMUTABLE — usar NuevaVersion para cambiar
    public bool AplicaIVA { get; }
    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 TipoDeIva(
        int id,
        string codigo,
        string descripcion,
        decimal porcentaje,
        bool aplicaIVA,
        bool activo,
        DateOnly vigenciaDesde,
        DateOnly? vigenciaHasta,
        int? predecesorId,
        DateTime fechaCreacion,
        DateTime? fechaModificacion)
    {
        Id = id;
        Codigo = codigo;
        Descripcion = descripcion;
        Porcentaje = porcentaje;
        AplicaIVA = aplicaIVA;
        Activo = activo;
        VigenciaDesde = vigenciaDesde;
        VigenciaHasta = vigenciaHasta;
        PredecesorId = predecesorId;
        FechaCreacion = fechaCreacion;
        FechaModificacion = fechaModificacion;
    }

    /// <summary>
    /// Factory para crear un nuevo TipoDeIva (Id=0 — BD asigna via IDENTITY; Activo=true).
    /// </summary>
    /// <exception cref="ArgumentException">Si Codigo no cumple formato o Porcentaje fuera de rango.</exception>
    public static TipoDeIva ForCreation(
        string codigo,
        string descripcion,
        decimal porcentaje,
        bool aplicaIVA,
        DateOnly vigenciaDesde,
        DateOnly? vigenciaHasta = null,
        int? predecesorId = null)
    {
        ValidateCodigo(codigo);
        ValidatePorcentaje(porcentaje, nameof(porcentaje));
        ValidateVigencias(vigenciaDesde, vigenciaHasta);

        return new(
            id: 0,
            codigo: codigo,
            descripcion: descripcion,
            porcentaje: porcentaje,
            aplicaIVA: aplicaIVA,
            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 TipoDeIva FromDb(
        int id,
        string codigo,
        string descripcion,
        decimal porcentaje,
        bool aplicaIVA,
        bool activo,
        DateOnly vigenciaDesde,
        DateOnly? vigenciaHasta,
        int? predecesorId,
        DateTime fechaCreacion,
        DateTime? fechaModificacion)
        => new(id, codigo, descripcion, porcentaje, aplicaIVA, activo,
               vigenciaDesde, vigenciaHasta, predecesorId, fechaCreacion, fechaModificacion);

    /// <summary>
    /// Crea una nueva versión con el porcentaje actualizado.
    /// 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 nuevoPorcentaje fuera de rango.</exception>
    /// <summary>
    /// Crea una nueva versión con el porcentaje actualizado.
    /// <param name="now">Timestamp UTC provisto por el caller (Application layer via TimeProvider).</param>
    /// </summary>
    public (TipoDeIva predecesoraCerrada, TipoDeIva nuevaVersion) NuevaVersion(
        decimal nuevoPorcentaje,
        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));

        ValidatePorcentaje(nuevoPorcentaje, nameof(nuevoPorcentaje));

        var cerrada = new TipoDeIva(
            id: Id,
            codigo: Codigo,
            descripcion: Descripcion,
            porcentaje: Porcentaje,
            aplicaIVA: AplicaIVA,
            activo: Activo,
            vigenciaDesde: VigenciaDesde,
            vigenciaHasta: vigenciaDesde.AddDays(-1),
            predecesorId: PredecesorId,
            fechaCreacion: FechaCreacion,
            fechaModificacion: now);

        var nueva = ForCreation(
            codigo: Codigo,
            descripcion: Descripcion,
            porcentaje: nuevoPorcentaje,
            aplicaIVA: AplicaIVA,
            vigenciaDesde: vigenciaDesde,
            vigenciaHasta: null,
            predecesorId: Id);

        return (cerrada, nueva);
    }

    // ── Cosmetic mutators (sealed With* — NOT WithPorcentaje) ─────────────────

    /// <summary>Actualiza la descripción. Porcentaje y vigencias permanecen inmutables.</summary>
    public TipoDeIva WithDescripcion(string descripcion, DateTime now)
        => new(Id, Codigo, descripcion, Porcentaje, AplicaIVA, Activo,
               VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, now);

    /// <summary>Actualiza el código. Porcentaje y vigencias permanecen inmutables.</summary>
    public TipoDeIva WithCodigo(string codigo, DateTime now)
        => new(Id, codigo, Descripcion, Porcentaje, AplicaIVA, Activo,
               VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, now);

    /// <summary>Actualiza la bandera AplicaIVA. Porcentaje permanece inmutable.</summary>
    public TipoDeIva WithAplicaIVA(bool aplicaIVA, DateTime now)
        => new(Id, Codigo, Descripcion, Porcentaje, aplicaIVA, Activo,
               VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, now);

    /// <summary>Retorna instancia con Activo=false.</summary>
    public TipoDeIva Deactivate(DateTime now)
        => new(Id, Codigo, Descripcion, Porcentaje, AplicaIVA, false,
               VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, now);

    /// <summary>Retorna instancia con Activo=true.</summary>
    public TipoDeIva Reactivate(DateTime now)
        => new(Id, Codigo, Descripcion, Porcentaje, AplicaIVA, true,
               VigenciaDesde, VigenciaHasta, PredecesorId, FechaCreacion, now);

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

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

    private static void ValidateCodigo(string codigo)
    {
        if (string.IsNullOrWhiteSpace(codigo) || !CodigoRegex.IsMatch(codigo))
            throw new ArgumentException(
                $"Codigo '{codigo}' inválido. Debe cumplir ^(EXENTO|NO_GRAVADO|IVA_\\d+)$.",
                nameof(codigo));
    }

    private static void ValidatePorcentaje(decimal porcentaje, string paramName)
    {
        if (porcentaje < 0m || porcentaje > 100m)
            throw new ArgumentException(
                $"Porcentaje ({porcentaje}) 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");
    }
}
