using Dapper; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Domain.Entities; namespace SIGCM2.Infrastructure.Persistence; public sealed class RubroRepository : IRubroRepository { private readonly SqlConnectionFactory _factory; public RubroRepository(SqlConnectionFactory factory) { _factory = factory; } public async Task AddAsync(Rubro rubro, CancellationToken ct = default) { // DF handles: Activo (1), FechaCreacion (SYSUTCDATETIME()), Orden (0 default — overridden if provided). const string sql = """ INSERT INTO dbo.Rubro (ParentId, Nombre, Orden, Activo, TarifarioBaseId) OUTPUT INSERTED.Id VALUES (@ParentId, @Nombre, @Orden, @Activo, @TarifarioBaseId) """; await using var connection = _factory.CreateConnection(); await connection.OpenAsync(ct); return await connection.ExecuteScalarAsync(sql, new { rubro.ParentId, rubro.Nombre, rubro.Orden, Activo = rubro.Activo ? 1 : 0, rubro.TarifarioBaseId, }); } public async Task GetByIdAsync(int id, CancellationToken ct = default) { const string sql = """ SELECT Id, ParentId, Nombre, Orden, Activo, TarifarioBaseId, FechaCreacion, FechaModificacion FROM dbo.Rubro WHERE Id = @Id """; await using var connection = _factory.CreateConnection(); await connection.OpenAsync(ct); var row = await connection.QuerySingleOrDefaultAsync(sql, new { Id = id }); return row is null ? null : MapRow(row); } public async Task> GetAllAsync(bool incluirInactivos, CancellationToken ct = default) { var sql = incluirInactivos ? """ SELECT Id, ParentId, Nombre, Orden, Activo, TarifarioBaseId, FechaCreacion, FechaModificacion FROM dbo.Rubro ORDER BY ParentId, Orden """ : """ SELECT Id, ParentId, Nombre, Orden, Activo, TarifarioBaseId, FechaCreacion, FechaModificacion FROM dbo.Rubro WHERE Activo = 1 ORDER BY ParentId, Orden """; await using var connection = _factory.CreateConnection(); await connection.OpenAsync(ct); var rows = await connection.QueryAsync(sql); return rows.Select(MapRow).ToList(); } public async Task> GetDescendantsAsync(int rootId, CancellationToken ct = default) { const string sql = """ WITH Descendants AS ( SELECT Id, ParentId, Nombre, Orden, Activo, TarifarioBaseId, FechaCreacion, FechaModificacion FROM dbo.Rubro WHERE ParentId = @RootId UNION ALL SELECT r.Id, r.ParentId, r.Nombre, r.Orden, r.Activo, r.TarifarioBaseId, r.FechaCreacion, r.FechaModificacion FROM dbo.Rubro r INNER JOIN Descendants d ON r.ParentId = d.Id ) SELECT * FROM Descendants """; await using var connection = _factory.CreateConnection(); await connection.OpenAsync(ct); var rows = await connection.QueryAsync(sql, new { RootId = rootId }); return rows.Select(MapRow).ToList(); } public async Task UpdateAsync(Rubro rubro, CancellationToken ct = default) { const string sql = """ UPDATE dbo.Rubro SET Nombre = @Nombre, ParentId = @ParentId, Orden = @Orden, Activo = @Activo, TarifarioBaseId = @TarifarioBaseId, FechaModificacion = @FechaModificacion WHERE Id = @Id """; await using var connection = _factory.CreateConnection(); await connection.OpenAsync(ct); await connection.ExecuteAsync(sql, new { rubro.Nombre, rubro.ParentId, rubro.Orden, Activo = rubro.Activo ? 1 : 0, rubro.TarifarioBaseId, rubro.FechaModificacion, rubro.Id, }); } public async Task CountActiveChildrenAsync(int id, CancellationToken ct = default) { const string sql = """ SELECT COUNT(1) FROM dbo.Rubro WHERE ParentId = @Id AND Activo = 1 """; await using var connection = _factory.CreateConnection(); await connection.OpenAsync(ct); return await connection.ExecuteScalarAsync(sql, new { Id = id }); } public async Task GetMaxOrdenAsync(int? parentId, CancellationToken ct = default) { // Returns MAX(Orden) + 1 among siblings, or 0 if no siblings exist. // Handler uses return value directly as the orden for the new Rubro. const string sql = """ SELECT ISNULL(MAX(Orden) + 1, 0) FROM dbo.Rubro WHERE (@ParentId IS NULL AND ParentId IS NULL) OR ParentId = @ParentId """; await using var connection = _factory.CreateConnection(); await connection.OpenAsync(ct); return await connection.ExecuteScalarAsync(sql, new { ParentId = parentId }); } public async Task ExistsByNombreUnderParentAsync( int? parentId, string nombre, int? excludeId, CancellationToken ct = default) { // Use UPPER() for explicit case-insensitive comparison. // DB collation is SQL_Latin1_General_CP1_CI_AI on Nombre column (already CI), // but UPPER() makes intent explicit and works regardless of collation. // The WHERE clause handles both root (NULL parent) and non-root cases. const string sql = """ SELECT COUNT(1) FROM dbo.Rubro WHERE ((@ParentId IS NULL AND ParentId IS NULL) OR ParentId = @ParentId) AND UPPER(Nombre) = UPPER(@Nombre) AND Activo = 1 AND (@ExcludeId IS NULL OR Id <> @ExcludeId) """; await using var connection = _factory.CreateConnection(); await connection.OpenAsync(ct); var count = await connection.ExecuteScalarAsync(sql, new { ParentId = parentId, Nombre = nombre, ExcludeId = excludeId, }); return count > 0; } public async Task GetDepthAsync(int? parentId, CancellationToken ct = default) { // If parentId is null, depth is 0 (creating a root node). if (!parentId.HasValue) return 0; // CTE walks up the ancestor chain from parentId to root, counting levels. // Each UNION ALL step goes one level up, so the count of rows = depth of parentId. const string sql = """ WITH Ancestors AS ( SELECT Id, ParentId, 1 AS Depth FROM dbo.Rubro WHERE Id = @ParentId UNION ALL SELECT r.Id, r.ParentId, a.Depth + 1 FROM dbo.Rubro r INNER JOIN Ancestors a ON r.Id = a.ParentId ) SELECT ISNULL(MAX(Depth), 0) FROM Ancestors """; await using var connection = _factory.CreateConnection(); await connection.OpenAsync(ct); return await connection.ExecuteScalarAsync(sql, new { ParentId = parentId.Value }); } // ── mapping ─────────────────────────────────────────────────────────────── private static Rubro MapRow(RubroRow r) => new( id: r.Id, parentId: r.ParentId, nombre: r.Nombre, orden: r.Orden, activo: r.Activo, tarifarioBaseId: r.TarifarioBaseId, fechaCreacion: r.FechaCreacion, fechaModificacion: r.FechaModificacion); private sealed record RubroRow( int Id, int? ParentId, string Nombre, int Orden, bool Activo, int? TarifarioBaseId, DateTime FechaCreacion, DateTime? FechaModificacion); }