using GestionIntegral.Api.Data; // Para DbConnectionFactory
using GestionIntegral.Api.Data.Repositories.Usuarios;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Usuarios;
using GestionIntegral.Api.Models.Usuarios;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Data; // Para IsolationLevel
using System.Linq;
using System.Threading.Tasks;

namespace GestionIntegral.Api.Services.Usuarios
{
    public class PerfilService : IPerfilService
    {
        private readonly IPerfilRepository _perfilRepository;
        private readonly DbConnectionFactory _connectionFactory; // Necesario para transacciones
        private readonly ILogger<PerfilService> _logger;
        private readonly IPermisoRepository _permisoRepository;

        public PerfilService(IPerfilRepository perfilRepository, IPermisoRepository permisoRepository, DbConnectionFactory connectionFactory, ILogger<PerfilService> logger)
        {
            _perfilRepository = perfilRepository;
            _permisoRepository = permisoRepository;
            _connectionFactory = connectionFactory; // Inyectar DbConnectionFactory
            _logger = logger;
        }

        private PerfilDto MapToDto(Perfil perfil) => new PerfilDto
        {
            Id = perfil.Id,
            NombrePerfil = perfil.NombrePerfil,
            Descripcion = perfil.Descripcion
        };

        public async Task<IEnumerable<PerfilDto>> ObtenerTodosAsync(string? nombreFilter)
        {
            var perfiles = await _perfilRepository.GetAllAsync(nombreFilter);
            return perfiles.Select(MapToDto);
        }

        public async Task<PerfilDto?> ObtenerPorIdAsync(int id)
        {
            var perfil = await _perfilRepository.GetByIdAsync(id);
            return perfil == null ? null : MapToDto(perfil);
        }

        public async Task<(PerfilDto? Perfil, string? Error)> CrearAsync(CreatePerfilDto createDto, int idUsuario)
        {
            if (await _perfilRepository.ExistsByNameAsync(createDto.NombrePerfil))
            {
                return (null, "El nombre del perfil ya existe.");
            }

            var nuevoPerfil = new Perfil { NombrePerfil = createDto.NombrePerfil, Descripcion = createDto.Descripcion };

            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);

            try
            {
                var perfilCreado = await _perfilRepository.CreateAsync(nuevoPerfil, idUsuario, transaction);
                if (perfilCreado == null) throw new DataException("La creación en el repositorio devolvió null.");

                transaction.Commit();
                _logger.LogInformation("Perfil ID {IdPerfil} creado por Usuario ID {IdUsuario}.", perfilCreado.Id, idUsuario);
                return (MapToDto(perfilCreado), null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback CrearAsync Perfil."); }
                _logger.LogError(ex, "Error CrearAsync Perfil. Nombre: {NombrePerfil}", createDto.NombrePerfil);
                return (null, $"Error interno al crear el perfil: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdatePerfilDto updateDto, int idUsuario)
        {
            // Verificar existencia ANTES de iniciar la transacción para evitar trabajo innecesario
            var perfilExistente = await _perfilRepository.GetByIdAsync(id);
            if (perfilExistente == null) return (false, "Perfil no encontrado.");

            if (await _perfilRepository.ExistsByNameAsync(updateDto.NombrePerfil, id))
            {
                return (false, "El nombre del perfil ya existe para otro registro.");
            }

            var perfilAActualizar = new Perfil { Id = id, NombrePerfil = updateDto.NombrePerfil, Descripcion = updateDto.Descripcion };

            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);

            try
            {
                var actualizado = await _perfilRepository.UpdateAsync(perfilAActualizar, idUsuario, transaction);
                if (!actualizado)
                {
                    // El repositorio ahora lanza KeyNotFoundException si no lo encuentra DENTRO de la tx.
                    // Si devuelve false sin excepción, podría ser otro error.
                    throw new DataException("La operación de actualización no afectó ninguna fila o falló.");
                }
                transaction.Commit();
                _logger.LogInformation("Perfil ID {IdPerfil} actualizado por Usuario ID {IdUsuario}.", id, idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException knfex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback ActualizarAsync Perfil (KeyNotFound)."); }
                _logger.LogWarning(knfex, "Intento de actualizar Perfil ID: {Id} no encontrado dentro de la transacción.", id);
                return (false, "Perfil no encontrado.");
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback ActualizarAsync Perfil."); }
                _logger.LogError(ex, "Error ActualizarAsync Perfil ID: {Id}", id);
                return (false, $"Error interno al actualizar el perfil: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
        {
            // Verificar existencia y si está en uso ANTES de la transacción
            var perfilExistente = await _perfilRepository.GetByIdAsync(id);
            if (perfilExistente == null) return (false, "Perfil no encontrado.");

            if (await _perfilRepository.IsInUseAsync(id))
            {
                return (false, "No se puede eliminar. El perfil está asignado a usuarios o permisos.");
            }

            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);

            try
            {
                var eliminado = await _perfilRepository.DeleteAsync(id, idUsuario, transaction);
                if (!eliminado)
                {
                    throw new DataException("La operación de eliminación no afectó ninguna fila o falló.");
                }
                transaction.Commit();
                _logger.LogInformation("Perfil ID {IdPerfil} eliminado por Usuario ID {IdUsuario}.", id, idUsuario);
                return (true, null);
            }
            catch (KeyNotFoundException knfex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback EliminarAsync Perfil (KeyNotFound)."); }
                _logger.LogWarning(knfex, "Intento de eliminar Perfil ID: {Id} no encontrado dentro de la transacción.", id);
                return (false, "Perfil no encontrado.");
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback EliminarAsync Perfil."); }
                _logger.LogError(ex, "Error EliminarAsync Perfil ID: {Id}", id);
                return (false, $"Error interno al eliminar el perfil: {ex.Message}");
            }
        }
        public async Task<IEnumerable<PermisoAsignadoDto>> ObtenerPermisosAsignadosAsync(int idPerfil)
        {
            // 1. Obtener todos los permisos definidos en el sistema
            var todosLosPermisos = await _permisoRepository.GetAllAsync(null, null); // Sin filtros

            // 2. Obtener los IDs de los permisos actualmente asignados a este perfil
            var idsPermisosAsignados = (await _perfilRepository.GetPermisoIdsByPerfilIdAsync(idPerfil)).ToHashSet();

            // 3. Mapear a DTO, marcando 'Asignado'
            var resultado = todosLosPermisos.Select(p => new PermisoAsignadoDto
            {
                Id = p.Id,
                Modulo = p.Modulo,
                DescPermiso = p.DescPermiso,
                CodAcc = p.CodAcc,
                Asignado = idsPermisosAsignados.Contains(p.Id)
            }).OrderBy(p => p.Modulo).ThenBy(p => p.DescPermiso); // Ordenar para la UI

            return resultado;
        }

        public async Task<(bool Exito, string? Error)> ActualizarPermisosAsignadosAsync(
    int idPerfil,
    ActualizarPermisosPerfilRequestDto request,
    int idUsuarioModificador)
        {
            // Validación: Verificar que el perfil exista
            var perfil = await _perfilRepository.GetByIdAsync(idPerfil);
            if (perfil == null)
            {
                return (false, "Perfil no encontrado.");
            }

            // --- INICIO DE LA LÓGICA DE AUDITORÍA ---

            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted);

            try
            {
                // 1. Obtener los IDs de permisos actuales DENTRO de la transacción para evitar race conditions
                var idsActuales = (await _perfilRepository.GetPermisoIdsByPerfilIdAsync(idPerfil)).ToHashSet();
                var idsNuevos = (request.PermisosIds ?? new List<int>()).ToHashSet();

                // 2. Calcular las diferencias
                var permisosAñadidos = idsNuevos.Where(id => !idsActuales.Contains(id)).ToList();
                var permisosQuitados = idsActuales.Where(id => !idsNuevos.Contains(id)).ToList();

                // 3. Registrar en el historial
                foreach (var idPermisoAñadido in permisosAñadidos)
                {
                    await _perfilRepository.LogPermisoAsignacionHistorialAsync(idPerfil, idPermisoAñadido, idUsuarioModificador, "Asignado", transaction);
                }
                foreach (var idPermisoQuitado in permisosQuitados)
                {
                    await _perfilRepository.LogPermisoAsignacionHistorialAsync(idPerfil, idPermisoQuitado, idUsuarioModificador, "Removido", transaction);
                }
                // --- FIN DE LA LÓGICA DE AUDITORÍA ---


                // 4. Actualizar la tabla principal (tu lógica actual)
                await _perfilRepository.UpdatePermisosByPerfilIdAsync(idPerfil, idsNuevos, transaction);

                transaction.Commit();
                _logger.LogInformation("Permisos actualizados para Perfil ID {IdPerfil} por Usuario ID {IdUsuarioModificador}. Añadidos: {CountAdded}, Quitados: {CountRemoved}",
                    idPerfil, idUsuarioModificador, permisosAñadidos.Count, permisosQuitados.Count);

                return (true, null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback ActualizarPermisosAsignadosAsync."); }
                _logger.LogError(ex, "Error al actualizar permisos para Perfil ID: {IdPerfil}", idPerfil);
                return (false, $"Error interno al actualizar los permisos del perfil: {ex.Message}");
            }
        }

        public async Task<IEnumerable<PerfilHistorialDto>> ObtenerHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idPerfilAfectado)
        {
            var historialData = await _perfilRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPerfilAfectado);

            return historialData.Select(h => new PerfilHistorialDto
            {
                IdPerfil = h.Historial.IdPerfil,
                Perfil = h.Historial.Perfil,
                DescPerfil = h.Historial.DescPerfil,
                Id_Usuario = h.Historial.Id_Usuario,
                NombreUsuarioModifico = h.NombreUsuarioModifico,
                FechaMod = h.Historial.FechaMod,
                TipoMod = h.Historial.TipoMod
            }).ToList();
        }

        public async Task<IEnumerable<PermisosPerfilesHistorialDto>> ObtenerPermisosAsignadosHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idPerfilAfectado, int? idPermisoAfectado)
        {
            var historialData = await _perfilRepository.GetPermisosAsignadosHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPerfilAfectado, idPermisoAfectado);

            return historialData.Select(h => new PermisosPerfilesHistorialDto
            {
                IdHistorial = h.Historial.Id,
                IdPerfil = h.Historial.idPerfil,
                NombrePerfil = h.NombrePerfil,
                IdPermiso = h.Historial.idPermiso,
                DescPermiso = h.DescPermiso,
                CodAccPermiso = h.CodAccPermiso,
                Id_Usuario = h.Historial.Id_Usuario,
                NombreUsuarioModifico = h.NombreUsuarioModifico,
                FechaMod = h.Historial.FechaMod,
                TipoMod = h.Historial.TipoMod
            }).ToList();
        }
    }
}