using Dapper;
using GestionIntegral.Api.Models.Usuarios;
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
using System.Data;
using System.Text;

namespace GestionIntegral.Api.Data.Repositories.Usuarios
{
    public class UsuarioRepository : IUsuarioRepository
    {
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<UsuarioRepository> _logger;

        public UsuarioRepository(DbConnectionFactory connectionFactory, ILogger<UsuarioRepository> logger)
        {
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        public async Task<IEnumerable<Usuario>> GetAllAsync(string? userFilter, string? nombreFilter)
        {
            var sqlBuilder = new StringBuilder("SELECT * FROM dbo.gral_Usuarios WHERE 1=1");
            var parameters = new DynamicParameters();

            if (!string.IsNullOrWhiteSpace(userFilter))
            {
                sqlBuilder.Append(" AND [User] LIKE @UserParam");
                parameters.Add("UserParam", $"%{userFilter}%");
            }
            if (!string.IsNullOrWhiteSpace(nombreFilter))
            {
                sqlBuilder.Append(" AND (Nombre LIKE @NombreParam OR Apellido LIKE @NombreParam)");
                parameters.Add("NombreParam", $"%{nombreFilter}%");
            }
            sqlBuilder.Append(" ORDER BY [User];");

            try
            {
                using var connection = _connectionFactory.CreateConnection();
                return await connection.QueryAsync<Usuario>(sqlBuilder.ToString(), parameters);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener todos los Usuarios.");
                return Enumerable.Empty<Usuario>();
            }
        }

        public async Task<IEnumerable<(Usuario Usuario, string NombrePerfil)>> GetAllWithProfileNameAsync(string? userFilter, string? nombreFilter)
        {
            var sqlBuilder = new StringBuilder(@"
                SELECT u.*, p.perfil AS NombrePerfil
                FROM dbo.gral_Usuarios u
                INNER JOIN dbo.gral_Perfiles p ON u.IdPerfil = p.id
                WHERE 1=1");
            var parameters = new DynamicParameters();

            if (!string.IsNullOrWhiteSpace(userFilter))
            {
                sqlBuilder.Append(" AND u.[User] LIKE @UserParam");
                parameters.Add("UserParam", $"%{userFilter}%");
            }
            if (!string.IsNullOrWhiteSpace(nombreFilter))
            {
                sqlBuilder.Append(" AND (u.Nombre LIKE @NombreParam OR u.Apellido LIKE @NombreParam)");
                parameters.Add("NombreParam", $"%{nombreFilter}%");
            }
            sqlBuilder.Append(" ORDER BY u.[User];");

            try
            {
                using var connection = _connectionFactory.CreateConnection();
                return await connection.QueryAsync<Usuario, string, (Usuario, string)>(
                    sqlBuilder.ToString(),
                    (usuario, nombrePerfil) => (usuario, nombrePerfil),
                    parameters,
                    splitOn: "NombrePerfil"
                );
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener todos los Usuarios con nombre de perfil.");
                return Enumerable.Empty<(Usuario, string)>();
            }
        }

        public async Task<Usuario?> GetByIdAsync(int id)
        {
            const string sql = "SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id";
            try
            {
                using var connection = _connectionFactory.CreateConnection();
                return await connection.QuerySingleOrDefaultAsync<Usuario>(sql, new { Id = id });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener Usuario por ID: {UsuarioId}", id);
                return null;
            }
        }

        public async Task<IEnumerable<Usuario>> GetByIdsAsync(IEnumerable<int> ids)
        {
            // 1. Validar si la lista de IDs está vacía para evitar una consulta innecesaria a la BD.
            if (ids == null || !ids.Any())
            {
                return Enumerable.Empty<Usuario>();
            }

            // 2. Definir la consulta. Dapper manejará la expansión de la cláusula IN de forma segura.
            const string sql = "SELECT * FROM dbo.gral_Usuarios WHERE Id IN @Ids";

            try
            {
                // 3. Crear conexión y ejecutar la consulta.
                using var connection = _connectionFactory.CreateConnection();
                // 4. Pasar la colección de IDs como parámetro. El nombre 'Ids' debe coincidir con el placeholder '@Ids'.
                return await connection.QueryAsync<Usuario>(sql, new { Ids = ids });
            }
            catch (Exception ex)
            {
                // 5. Registrar el error y devolver una lista vacía en caso de fallo para no romper la aplicación.
                _logger.LogError(ex, "Error al obtener Usuarios por lista de IDs.");
                return Enumerable.Empty<Usuario>();
            }
        }

        public async Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id)
        {
            const string sql = @"
                SELECT u.*, p.perfil AS NombrePerfil
                FROM dbo.gral_Usuarios u
                INNER JOIN dbo.gral_Perfiles p ON u.IdPerfil = p.id
                WHERE u.Id = @Id";
            try
            {
                using var connection = _connectionFactory.CreateConnection();
                var result = await connection.QueryAsync<Usuario, string, (Usuario Usuario, string NombrePerfil)>(
                    sql,
                    (usuario, nombrePerfil) => (usuario, nombrePerfil),
                    new { Id = id },
                    splitOn: "NombrePerfil"
                );
                return result.SingleOrDefault();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener Usuario por ID con nombre de perfil: {UsuarioId}", id);
                return (null, null);
            }
        }

        public async Task<Usuario?> GetByUsernameAsync(string username)
        {
            // Esta es la misma que en AuthRepository, si se unifican, se puede eliminar una.
            const string sql = "SELECT * FROM dbo.gral_Usuarios WHERE [User] = @Username";
            try
            {
                using var connection = _connectionFactory.CreateConnection();
                return await connection.QuerySingleOrDefaultAsync<Usuario>(sql, new { Username = username });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener Usuario por Username: {Username}", username);
                return null;
            }
        }

        public async Task<bool> UserExistsAsync(string username, int? excludeId = null)
        {
            var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.gral_Usuarios WHERE [User] = @Username");
            var parameters = new DynamicParameters();
            parameters.Add("Username", username);

            if (excludeId.HasValue)
            {
                sqlBuilder.Append(" AND Id != @ExcludeId");
                parameters.Add("ExcludeId", excludeId.Value);
            }
            try
            {
                using var connection = _connectionFactory.CreateConnection();
                return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error en UserExistsAsync para username: {Username}", username);
                return true; // Asumir que existe para prevenir duplicados
            }
        }

        public async Task<Usuario?> CreateAsync(Usuario nuevoUsuario, int idUsuarioCreador, IDbTransaction transaction)
        {
            const string sqlInsert = @"
                INSERT INTO dbo.gral_Usuarios ([User], ClaveHash, ClaveSalt, Habilitada, SupAdmin, Nombre, Apellido, IdPerfil, VerLog, DebeCambiarClave)
                OUTPUT INSERTED.*
                VALUES (@User, @ClaveHash, @ClaveSalt, @Habilitada, @SupAdmin, @Nombre, @Apellido, @IdPerfil, @VerLog, @DebeCambiarClave);";

            const string sqlInsertHistorico = @"
                INSERT INTO dbo.gral_Usuarios_H
                    (IdUsuario, UserNvo, HabilitadaNva, SupAdminNvo, NombreNvo, ApellidoNvo, IdPerfilNvo, DebeCambiarClaveNva, Id_UsuarioMod, FechaMod, TipoMod)
                VALUES (@IdUsuarioHist, @UserNvoHist, @HabilitadaNvaHist, @SupAdminNvoHist, @NombreNvoHist, @ApellidoNvoHist, @IdPerfilNvoHist, @DebeCambiarClaveNvaHist, @IdUsuarioModHist, @FechaModHist, @TipoModHist);";

            var connection = transaction.Connection!;
            var insertedUsuario = await connection.QuerySingleAsync<Usuario>(sqlInsert, nuevoUsuario, transaction);

            if (insertedUsuario == null) throw new DataException("No se pudo crear el usuario.");

            await connection.ExecuteAsync(sqlInsertHistorico, new
            {
                IdUsuarioHist = insertedUsuario.Id,
                UserNvoHist = insertedUsuario.User,
                HabilitadaNvaHist = insertedUsuario.Habilitada,
                SupAdminNvoHist = insertedUsuario.SupAdmin,
                NombreNvoHist = insertedUsuario.Nombre,
                ApellidoNvoHist = insertedUsuario.Apellido,
                IdPerfilNvoHist = insertedUsuario.IdPerfil,
                DebeCambiarClaveNvaHist = insertedUsuario.DebeCambiarClave,
                IdUsuarioModHist = idUsuarioCreador,
                FechaModHist = DateTime.Now,
                TipoModHist = "Creado"
            }, transaction);

            return insertedUsuario;
        }

        public async Task<bool> UpdateAsync(Usuario usuarioAActualizar, int idUsuarioModificador, IDbTransaction transaction)
        {
            var connection = transaction.Connection!;
            var usuarioActual = await connection.QuerySingleOrDefaultAsync<Usuario>(
                "SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id", new { usuarioAActualizar.Id }, transaction);

            if (usuarioActual == null) throw new KeyNotFoundException("Usuario no encontrado para actualizar.");

            // User (nombre de usuario) no se actualiza aquí
            const string sqlUpdate = @"
                UPDATE dbo.gral_Usuarios SET
                    Habilitada = @Habilitada, SupAdmin = @SupAdmin, Nombre = @Nombre, Apellido = @Apellido,
                    IdPerfil = @IdPerfil, VerLog = @VerLog, DebeCambiarClave = @DebeCambiarClave
                WHERE Id = @Id;";

            const string sqlInsertHistorico = @"
                INSERT INTO dbo.gral_Usuarios_H
                    (IdUsuario, UserAnt, UserNvo, HabilitadaAnt, HabilitadaNva, SupAdminAnt, SupAdminNvo,
                     NombreAnt, NombreNvo, ApellidoAnt, ApellidoNvo, IdPerfilAnt, IdPerfilNvo,
                     DebeCambiarClaveAnt, DebeCambiarClaveNva, Id_UsuarioMod, FechaMod, TipoMod)
                VALUES (@IdUsuarioHist, @UserAntHist, @UserNvoHist, @HabilitadaAntHist, @HabilitadaNvaHist, @SupAdminAntHist, @SupAdminNvoHist,
                        @NombreAntHist, @NombreNvoHist, @ApellidoAntHist, @ApellidoNvoHist, @IdPerfilAntHist, @IdPerfilNvoHist,
                        @DebeCambiarClaveAntHist, @DebeCambiarClaveNvaHist, @IdUsuarioModHist, @FechaModHist, @TipoModHist);";

            await connection.ExecuteAsync(sqlInsertHistorico, new
            {
                IdUsuarioHist = usuarioActual.Id,
                UserAntHist = usuarioActual.User,
                UserNvoHist = usuarioAActualizar.User, // Aunque no cambiemos User, lo registramos
                HabilitadaAntHist = usuarioActual.Habilitada,
                HabilitadaNvaHist = usuarioAActualizar.Habilitada,
                SupAdminAntHist = usuarioActual.SupAdmin,
                SupAdminNvoHist = usuarioAActualizar.SupAdmin,
                NombreAntHist = usuarioActual.Nombre,
                NombreNvoHist = usuarioAActualizar.Nombre,
                ApellidoAntHist = usuarioActual.Apellido,
                ApellidoNvoHist = usuarioAActualizar.Apellido,
                IdPerfilAntHist = usuarioActual.IdPerfil,
                IdPerfilNvoHist = usuarioAActualizar.IdPerfil,
                DebeCambiarClaveAntHist = usuarioActual.DebeCambiarClave,
                DebeCambiarClaveNvaHist = usuarioAActualizar.DebeCambiarClave,
                IdUsuarioModHist = idUsuarioModificador,
                FechaModHist = DateTime.Now,
                TipoModHist = "Actualizado"
            }, transaction);

            var rowsAffected = await connection.ExecuteAsync(sqlUpdate, usuarioAActualizar, transaction);
            return rowsAffected == 1;
        }

        public async Task<bool> SetPasswordAsync(int userId, string newHash, string newSalt, bool debeCambiarClave, int idUsuarioModificador, IDbTransaction transaction)
        {
            var connection = transaction.Connection!;
            var usuarioActual = await connection.QuerySingleOrDefaultAsync<Usuario>(
               "SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id", new { Id = userId }, transaction);

            if (usuarioActual == null) throw new KeyNotFoundException("Usuario no encontrado para cambiar contraseña.");

            const string sqlUpdate = @"UPDATE dbo.gral_Usuarios
                                       SET ClaveHash = @ClaveHash, ClaveSalt = @ClaveSalt, DebeCambiarClave = @DebeCambiarClave
                                       WHERE Id = @UserId";
            const string sqlInsertHistorico = @"
                INSERT INTO dbo.gral_Usuarios_H
                    (IdUsuario, UserNvo, HabilitadaNva, SupAdminNvo, NombreNvo, ApellidoNvo, IdPerfilNvo,
                     DebeCambiarClaveAnt, DebeCambiarClaveNva, Id_UsuarioMod, FechaMod, TipoMod)
                VALUES (@IdUsuarioHist, @UserNvoHist, @HabilitadaNvaHist, @SupAdminNvoHist, @NombreNvoHist, @ApellidoNvoHist, @IdPerfilNvoHist,
                        @DebeCambiarClaveAntHist, @DebeCambiarClaveNvaHist, @IdUsuarioModHist, @FechaModHist, @TipoModHist);";


            await connection.ExecuteAsync(sqlInsertHistorico, new
            {
                IdUsuarioHist = usuarioActual.Id,
                UserNvoHist = usuarioActual.User, // No cambia el user
                HabilitadaNvaHist = usuarioActual.Habilitada,
                SupAdminNvoHist = usuarioActual.SupAdmin,
                NombreNvoHist = usuarioActual.Nombre,
                ApellidoNvoHist = usuarioActual.Apellido,
                IdPerfilNvoHist = usuarioActual.IdPerfil,
                DebeCambiarClaveAntHist = usuarioActual.DebeCambiarClave, // Estado anterior de este flag
                DebeCambiarClaveNvaHist = debeCambiarClave,             // Nuevo estado de este flag
                IdUsuarioModHist = idUsuarioModificador,
                FechaModHist = DateTime.Now,
                TipoModHist = "Clave Cambiada" // O "Clave Reseteada"
            }, transaction);

            var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new
            {
                ClaveHash = newHash,
                ClaveSalt = newSalt,
                DebeCambiarClave = debeCambiarClave,
                UserId = userId
            }, transaction);
            return rowsAffected == 1;
        }

        public async Task<IEnumerable<UsuarioHistorialDto>> GetHistorialByUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta)
        {
            var sqlBuilder = new StringBuilder(@"
        SELECT
            h.IdHist,
            h.IdUsuario AS IdUsuarioAfectado,
            uPrincipal.[User] AS UserAfectado, -- DELIMITADO CON []
            h.UserAnt, h.UserNvo,
            h.HabilitadaAnt, h.HabilitadaNva,
            h.SupAdminAnt, h.SupAdminNvo,
            h.NombreAnt, h.NombreNvo,
            h.ApellidoAnt, h.ApellidoNvo,
            h.IdPerfilAnt, h.IdPerfilNvo,
            pAnt.perfil AS NombrePerfilAnt,
            pNvo.perfil AS NombrePerfilNvo,
            h.DebeCambiarClaveAnt, h.DebeCambiarClaveNva,
            h.Id_UsuarioMod AS IdUsuarioModifico,
            ISNULL(uMod.Nombre + ' ' + uMod.Apellido, 'Sistema') AS NombreUsuarioModifico,
            h.FechaMod AS FechaModificacion,
            h.TipoMod AS TipoModificacion
        FROM dbo.gral_Usuarios_H h
        INNER JOIN dbo.gral_Usuarios uPrincipal ON h.IdUsuario = uPrincipal.Id -- No necesita delimitador aquí si 'Id' es el nombre de la columna PK en gral_Usuarios
        LEFT JOIN dbo.gral_Usuarios uMod ON h.Id_UsuarioMod = uMod.Id
        LEFT JOIN dbo.gral_Perfiles pAnt ON h.IdPerfilAnt = pAnt.id
        LEFT JOIN dbo.gral_Perfiles pNvo ON h.IdPerfilNvo = pNvo.id
        WHERE h.IdUsuario = @IdUsuarioAfectadoParam");

            var parameters = new DynamicParameters();
            parameters.Add("IdUsuarioAfectadoParam", idUsuarioAfectado);

            if (fechaDesde.HasValue)
            {
                sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam");
                parameters.Add("FechaDesdeParam", fechaDesde.Value.Date);
            }
            if (fechaHasta.HasValue)
            {
                sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam");
                parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); // Hasta el final del día
            }
            sqlBuilder.Append(" ORDER BY h.FechaMod DESC, h.IdHist DESC;");

            try
            {
                using var connection = _connectionFactory.CreateConnection();
                return await connection.QueryAsync<UsuarioHistorialDto>(sqlBuilder.ToString(), parameters);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener historial para Usuario ID: {IdUsuarioAfectado}", idUsuarioAfectado);
                return Enumerable.Empty<UsuarioHistorialDto>();
            }
        }

        public async Task<IEnumerable<UsuarioHistorialDto>> GetAllHistorialAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModificoFilter, string? tipoModFilter)
        {
            var sqlBuilder = new StringBuilder(@"
        SELECT
            h.IdHist,
            h.IdUsuario AS IdUsuarioAfectado,
            uPrincipal.[User] AS UserAfectado, -- DELIMITADO CON []
            h.UserAnt, h.UserNvo,
            h.HabilitadaAnt, h.HabilitadaNva,
            h.SupAdminAnt, h.SupAdminNvo,
            h.NombreAnt, h.NombreNvo,
            h.ApellidoAnt, h.ApellidoNvo,
            h.IdPerfilAnt, h.IdPerfilNvo,
            pAnt.perfil AS NombrePerfilAnt,
            pNvo.perfil AS NombrePerfilNvo,
            h.DebeCambiarClaveAnt, h.DebeCambiarClaveNva,
            h.Id_UsuarioMod AS IdUsuarioModifico,
            ISNULL(uMod.Nombre + ' ' + uMod.Apellido, 'Sistema') AS NombreUsuarioModifico,
            h.FechaMod AS FechaModificacion,
            h.TipoMod AS TipoModificacion
        FROM dbo.gral_Usuarios_H h
        INNER JOIN dbo.gral_Usuarios uPrincipal ON h.IdUsuario = uPrincipal.Id
        LEFT JOIN dbo.gral_Usuarios uMod ON h.Id_UsuarioMod = uMod.Id
        LEFT JOIN dbo.gral_Perfiles pAnt ON h.IdPerfilAnt = pAnt.id
        LEFT JOIN dbo.gral_Perfiles pNvo ON h.IdPerfilNvo = pNvo.id
        WHERE 1=1");

            var parameters = new DynamicParameters();

            if (fechaDesde.HasValue)
            {
                sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam");
                parameters.Add("FechaDesdeParam", fechaDesde.Value.Date);
            }
            if (fechaHasta.HasValue)
            {
                sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam");
                parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1));
            }
            if (idUsuarioModificoFilter.HasValue)
            {
                sqlBuilder.Append(" AND h.Id_UsuarioMod = @IdUsuarioModFilterParam");
                parameters.Add("IdUsuarioModFilterParam", idUsuarioModificoFilter.Value);
            }
            if (!string.IsNullOrWhiteSpace(tipoModFilter))
            {
                sqlBuilder.Append(" AND h.TipoMod LIKE @TipoModFilterParam");
                parameters.Add("TipoModFilterParam", $"%{tipoModFilter}%");
            }
            sqlBuilder.Append(" ORDER BY h.FechaMod DESC, h.IdHist DESC;");

            try
            {
                using var connection = _connectionFactory.CreateConnection();
                return await connection.QueryAsync<UsuarioHistorialDto>(sqlBuilder.ToString(), parameters);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener todo el historial de Usuarios.");
                return Enumerable.Empty<UsuarioHistorialDto>();
            }
        }
    }
}