using GestionIntegral.Api.Data;
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;
using System.Linq;
using System.Threading.Tasks;

namespace GestionIntegral.Api.Services.Usuarios
{
    public class PermisoService : IPermisoService
    {
        private readonly IPermisoRepository _permisoRepository;
        private readonly DbConnectionFactory _connectionFactory; // Inyectar para transacciones
        private readonly ILogger<PermisoService> _logger;

        public PermisoService(IPermisoRepository permisoRepository, DbConnectionFactory connectionFactory, ILogger<PermisoService> logger)
        {
            _permisoRepository = permisoRepository;
            _connectionFactory = connectionFactory; // Asignar
            _logger = logger;
        }

        private PermisoDto MapToDto(Permiso permiso) => new PermisoDto
        {
            Id = permiso.Id,
            Modulo = permiso.Modulo,
            DescPermiso = permiso.DescPermiso,
            CodAcc = permiso.CodAcc
        };

        public async Task<IEnumerable<PermisoDto>> ObtenerTodosAsync(string? moduloFilter, string? codAccFilter)
        {
            var permisos = await _permisoRepository.GetAllAsync(moduloFilter, codAccFilter);
            return permisos.Select(MapToDto);
        }

        public async Task<PermisoDto?> ObtenerPorIdAsync(int id)
        {
            var permiso = await _permisoRepository.GetByIdAsync(id);
            return permiso == null ? null : MapToDto(permiso);
        }

        public async Task<(PermisoDto? Permiso, string? Error)> CrearAsync(CreatePermisoDto createDto, int idUsuario)
        {
            if (await _permisoRepository.ExistsByCodAccAsync(createDto.CodAcc))
            {
                return (null, "El código de acceso (CodAcc) ya existe.");
            }

            var nuevoPermiso = new Permiso
            {
                Modulo = createDto.Modulo,
                DescPermiso = createDto.DescPermiso,
                CodAcc = createDto.CodAcc
            };

            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 permisoCreado = await _permisoRepository.CreateAsync(nuevoPermiso, idUsuario, transaction);
                if (permisoCreado == null) throw new DataException("La creación en el repositorio devolvió null.");

                transaction.Commit();
                _logger.LogInformation("Permiso ID {IdPermiso} creado por Usuario ID {IdUsuario}.", permisoCreado.Id, idUsuario);
                return (MapToDto(permisoCreado), null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback CrearAsync Permiso."); }
                _logger.LogError(ex, "Error CrearAsync Permiso. CodAcc: {CodAcc}", createDto.CodAcc);
                return (null, $"Error interno al crear el permiso: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdatePermisoDto updateDto, int idUsuario)
        {
            var permisoExistente = await _permisoRepository.GetByIdAsync(id); // Verificar existencia fuera de la TX
            if (permisoExistente == null) return (false, "Permiso no encontrado.");

            if (await _permisoRepository.ExistsByCodAccAsync(updateDto.CodAcc, id))
            {
                return (false, "El código de acceso (CodAcc) ya existe para otro permiso.");
            }

            var permisoAActualizar = new Permiso
            {
                Id = id,
                Modulo = updateDto.Modulo,
                DescPermiso = updateDto.DescPermiso,
                CodAcc = updateDto.CodAcc
            };

            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 _permisoRepository.UpdateAsync(permisoAActualizar, idUsuario, transaction);
                if (!actualizado) throw new DataException("La operación de actualización no afectó ninguna fila.");

                transaction.Commit();
                _logger.LogInformation("Permiso ID {IdPermiso} 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 Permiso (KeyNotFound)."); }
                _logger.LogWarning(knfex, "Intento de actualizar Permiso ID: {Id} no encontrado dentro de la transacción.", id);
                return (false, "Permiso no encontrado.");
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback ActualizarAsync Permiso."); }
                _logger.LogError(ex, "Error ActualizarAsync Permiso ID: {Id}", id);
                return (false, $"Error interno al actualizar el permiso: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
        {
            var permisoExistente = await _permisoRepository.GetByIdAsync(id); // Verificar existencia fuera de la TX
            if (permisoExistente == null) return (false, "Permiso no encontrado.");

            if (await _permisoRepository.IsInUseAsync(id))
            {
                return (false, "No se puede eliminar. El permiso está asignado a uno o más perfiles.");
            }

            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 _permisoRepository.DeleteAsync(id, idUsuario, transaction);
                if (!eliminado) throw new DataException("La operación de eliminación no afectó ninguna fila.");

                transaction.Commit();
                _logger.LogInformation("Permiso ID {IdPermiso} 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 Permiso (KeyNotFound)."); }
                _logger.LogWarning(knfex, "Intento de eliminar Permiso ID: {Id} no encontrado dentro de la transacción.", id);
                return (false, "Permiso no encontrado.");
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error rollback EliminarAsync Permiso."); }
                _logger.LogError(ex, "Error EliminarAsync Permiso ID: {Id}", id);
                return (false, $"Error interno al eliminar el permiso: {ex.Message}");
            }
        }

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

            return historialData.Select(h => new PermisoHistorialDto
            {
                IdHistorial = h.Historial.IdHist,
                IdPermiso = h.Historial.IdPermiso,
                Modulo = h.Historial.Modulo,
                DescPermiso = h.Historial.DescPermiso,
                CodAcc = h.Historial.CodAcc,
                Id_Usuario = h.Historial.Id_Usuario,
                NombreUsuarioModifico = h.NombreUsuarioModifico,
                FechaMod = h.Historial.FechaMod,
                TipoMod = h.Historial.TipoMod
            }).ToList();
        }
    }
}