using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Impresion;
using GestionIntegral.Api.Dtos.Impresion;
using GestionIntegral.Api.Models.Impresion;
using GestionIntegral.Api.Models.Distribucion; // Para Publicacion, PubliSeccion
using GestionIntegral.Api.Data.Repositories.Distribucion;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using GestionIntegral.Api.Dtos.Auditoria;

namespace GestionIntegral.Api.Services.Impresion
{
    public class StockBobinaService : IStockBobinaService
    {
        private readonly IStockBobinaRepository _stockBobinaRepository;
        private readonly ITipoBobinaRepository _tipoBobinaRepository;
        private readonly IPlantaRepository _plantaRepository;
        private readonly IEstadoBobinaRepository _estadoBobinaRepository;
        private readonly IPublicacionRepository _publicacionRepository; // Para validar IdPublicacion
        private readonly IPubliSeccionRepository _publiSeccionRepository; // Para validar IdSeccion
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<StockBobinaService> _logger;

        public StockBobinaService(
            IStockBobinaRepository stockBobinaRepository,
            ITipoBobinaRepository tipoBobinaRepository,
            IPlantaRepository plantaRepository,
            IEstadoBobinaRepository estadoBobinaRepository,
            IPublicacionRepository publicacionRepository,
            IPubliSeccionRepository publiSeccionRepository,
            DbConnectionFactory connectionFactory,
            ILogger<StockBobinaService> logger)
        {
            _stockBobinaRepository = stockBobinaRepository;
            _tipoBobinaRepository = tipoBobinaRepository;
            _plantaRepository = plantaRepository;
            _estadoBobinaRepository = estadoBobinaRepository;
            _publicacionRepository = publicacionRepository;
            _publiSeccionRepository = publiSeccionRepository;
            _connectionFactory = connectionFactory;
            _logger = logger;
        }

        // Mapeo complejo porque necesitamos nombres de entidades relacionadas
        private async Task<StockBobinaDto> MapToDto(StockBobina bobina)
        {
            if (bobina == null) return null!; // Debería ser manejado por el llamador

            var tipoBobina = await _tipoBobinaRepository.GetByIdAsync(bobina.IdTipoBobina);
            var planta = await _plantaRepository.GetByIdAsync(bobina.IdPlanta);
            var estado = await _estadoBobinaRepository.GetByIdAsync(bobina.IdEstadoBobina);
            Publicacion? publicacion = null;
            PubliSeccion? seccion = null;

            if (bobina.IdPublicacion.HasValue)
                publicacion = await _publicacionRepository.GetByIdSimpleAsync(bobina.IdPublicacion.Value);
            if (bobina.IdSeccion.HasValue)
                seccion = await _publiSeccionRepository.GetByIdAsync(bobina.IdSeccion.Value); // Asume que GetByIdAsync existe

            return new StockBobinaDto
            {
                IdBobina = bobina.IdBobina,
                IdTipoBobina = bobina.IdTipoBobina,
                NombreTipoBobina = tipoBobina?.Denominacion ?? "N/A",
                NroBobina = bobina.NroBobina,
                Peso = bobina.Peso,
                IdPlanta = bobina.IdPlanta,
                NombrePlanta = planta?.Nombre ?? "N/A",
                IdEstadoBobina = bobina.IdEstadoBobina,
                NombreEstadoBobina = estado?.Denominacion ?? "N/A",
                Remito = bobina.Remito,
                FechaRemito = bobina.FechaRemito.ToString("yyyy-MM-dd"),
                FechaEstado = bobina.FechaEstado?.ToString("yyyy-MM-dd"),
                IdPublicacion = bobina.IdPublicacion,
                NombrePublicacion = publicacion?.Nombre,
                IdSeccion = bobina.IdSeccion,
                NombreSeccion = seccion?.Nombre,
                Obs = bobina.Obs
            };
        }

        public async Task<IEnumerable<StockBobinaDto>> ObtenerTodosAsync(
            int? idTipoBobina, string? nroBobinaFilter, int? idPlanta,
            int? idEstadoBobina, string? remitoFilter, DateTime? fechaDesde, DateTime? fechaHasta, DateTime? fechaEstadoDesde, DateTime? fechaEstadoHasta)
        {
            var bobinas = await _stockBobinaRepository.GetAllAsync(idTipoBobina, nroBobinaFilter, idPlanta, idEstadoBobina, remitoFilter, fechaDesde, fechaHasta, fechaEstadoDesde, fechaEstadoHasta);
            var dtos = new List<StockBobinaDto>();
            foreach (var bobina in bobinas)
            {
                dtos.Add(await MapToDto(bobina));
            }
            return dtos;
        }

        public async Task<StockBobinaDto?> ObtenerPorIdAsync(int idBobina)
        {
            var bobina = await _stockBobinaRepository.GetByIdAsync(idBobina);
            return bobina == null ? null : await MapToDto(bobina);
        }

        public async Task<(StockBobinaDto? Bobina, string? Error)> IngresarBobinaAsync(CreateStockBobinaDto createDto, int idUsuario)
        {
            if (await _tipoBobinaRepository.GetByIdAsync(createDto.IdTipoBobina) == null)
                return (null, "Tipo de bobina inválido.");
            if (await _plantaRepository.GetByIdAsync(createDto.IdPlanta) == null)
                return (null, "Planta inválida.");
            if (await _stockBobinaRepository.GetByNroBobinaAsync(createDto.NroBobina) != null)
                return (null, $"El número de bobina '{createDto.NroBobina}' ya existe.");

            var nuevaBobina = new StockBobina
            {
                IdTipoBobina = createDto.IdTipoBobina,
                NroBobina = createDto.NroBobina,
                Peso = createDto.Peso,
                IdPlanta = createDto.IdPlanta,
                IdEstadoBobina = 1, // 1 = Disponible (según contexto previo)
                Remito = createDto.Remito,
                FechaRemito = createDto.FechaRemito.Date,
                FechaEstado = createDto.FechaRemito.Date, // Estado inicial en fecha de remito
                IdPublicacion = null,
                IdSeccion = null,
                Obs = null
            };

            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();
            try
            {
                var bobinaCreada = await _stockBobinaRepository.CreateAsync(nuevaBobina, idUsuario, transaction);
                if (bobinaCreada == null) throw new DataException("Error al ingresar la bobina.");
                transaction.Commit();
                _logger.LogInformation("Bobina ID {Id} ingresada por Usuario ID {UserId}.", bobinaCreada.IdBobina, idUsuario);
                return (await MapToDto(bobinaCreada), null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error IngresarBobinaAsync: {NroBobina}", createDto.NroBobina);
                return (null, $"Error interno: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> ActualizarDatosBobinaDisponibleAsync(int idBobina, UpdateStockBobinaDto updateDto, int idUsuario)
        {
            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();
            try
            {
                var bobinaExistente = await _stockBobinaRepository.GetByIdAsync(idBobina); // Obtener dentro de TX
                if (bobinaExistente == null) return (false, "Bobina no encontrada.");
                if (bobinaExistente.IdEstadoBobina != 1) // Solo se pueden editar datos si está "Disponible"
                    return (false, "Solo se pueden modificar los datos de una bobina en estado 'Disponible'.");

                // Validar unicidad de NroBobina si cambió
                if (bobinaExistente.NroBobina != updateDto.NroBobina &&
                    await _stockBobinaRepository.GetByNroBobinaAsync(updateDto.NroBobina) != null) // Validar fuera de TX o pasar TX al repo
                {
                    try { transaction.Rollback(); } catch { } // Rollback antes de retornar por validación
                    return (false, $"El nuevo número de bobina '{updateDto.NroBobina}' ya existe.");
                }
                if (await _tipoBobinaRepository.GetByIdAsync(updateDto.IdTipoBobina) == null)
                    return (false, "Tipo de bobina inválido.");
                //if (await _plantaRepository.GetByIdAsync(updateDto.IdPlanta) == null)
                //    return (false, "Planta inválida.");


                bobinaExistente.IdTipoBobina = updateDto.IdTipoBobina;
                bobinaExistente.NroBobina = updateDto.NroBobina;
                bobinaExistente.Peso = updateDto.Peso;
                //bobinaExistente.IdPlanta = updateDto.IdPlanta;
                //bobinaExistente.Remito = updateDto.Remito;
                //bobinaExistente.FechaRemito = updateDto.FechaRemito.Date;
                // FechaEstado se mantiene ya que el estado no cambia aquí

                var actualizado = await _stockBobinaRepository.UpdateAsync(bobinaExistente, idUsuario, transaction, "Datos Actualizados");
                if (!actualizado) throw new DataException("Error al actualizar la bobina.");
                transaction.Commit();
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error ActualizarDatosBobinaDisponibleAsync ID: {IdBobina}", idBobina);
                return (false, $"Error interno: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> CambiarEstadoBobinaAsync(int idBobina, CambiarEstadoBobinaDto cambiarEstadoDto, int idUsuario)
        {
            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();
            try
            {
                var bobina = await _stockBobinaRepository.GetByIdAsync(idBobina); 
                if (bobina == null)
                {
                    try { transaction.Rollback(); } catch { }
                    return (false, "Bobina no encontrada.");
                }
        
                // Comparamos solo las fechas (sin hora) para evitar problemas de precisión.
                if (cambiarEstadoDto.FechaCambioEstado.Date < bobina.FechaRemito.Date)
                {
                    try { transaction.Rollback(); } catch { }
                    return (false, $"Error de integridad: La fecha del nuevo estado ({cambiarEstadoDto.FechaCambioEstado:dd/MM/yyyy}) " +
                                   $"no puede ser anterior a la fecha de ingreso por remito ({bobina.FechaRemito:dd/MM/yyyy}).");
                }

                var nuevoEstado = await _estadoBobinaRepository.GetByIdAsync(cambiarEstadoDto.NuevoEstadoId);
                if (nuevoEstado == null)
                {
                    try { transaction.Rollback(); } catch { }
                    return (false, "El nuevo estado especificado no es válido.");
                }

                // --- INICIO DE VALIDACIONES DE FLUJO DE ESTADOS ---
                if (bobina.IdEstadoBobina == cambiarEstadoDto.NuevoEstadoId)
                {
                    try { transaction.Rollback(); } catch { }
                    return (false, "La bobina ya se encuentra en ese estado.");
                }

                // Reglas específicas para salir de "Dañada" (ID 3)
                if (bobina.IdEstadoBobina == 3) // 3 = Dañada
                {
                    if (cambiarEstadoDto.NuevoEstadoId != 1) // 1 = Disponible
                    {
                        try { transaction.Rollback(); } catch { }
                        return (false, "Una bobina dañada solo puede ser revertida a 'Disponible' (si fue un error).");
                    }
                    // Si se cambia de Dañada a Disponible, se limpiarán Publicacion y Seccion más abajo.
                }
                // Regla para salir de "Utilizada" (ID 2)
                else if (bobina.IdEstadoBobina == 2) // 2 = En Uso
                {
                    if (cambiarEstadoDto.NuevoEstadoId == 1) // No se puede volver a Disponible directamente
                    {
                        try { transaction.Rollback(); } catch { }
                        return (false, "Una bobina 'En Uso' no puede volver a 'Disponible' directamente mediante esta acción. Primero debe marcarse como Dañada si es necesario.");
                    }
                    // Si se cambia de Utilizada a cualquier otro estado que no sea Dañada (aunque aquí solo se permite Dañada),
                    // y si el nuevo estado NO es En Uso, también se deberían limpiar Publicacion y Seccion.
                    // La lógica actual ya lo hace si nuevoEstadoId no es 2.
                }
                // Regla para salir de "Disponible" (ID 1)
                else if (bobina.IdEstadoBobina == 1) // 1 = Disponible
                {
                    // Desde Disponible puede pasar a En Uso (2) o Dañada (3).
                    // El frontend ya debería filtrar esto, pero una validación aquí es buena.
                    if (cambiarEstadoDto.NuevoEstadoId != 2 && cambiarEstadoDto.NuevoEstadoId != 3)
                    {
                        try { transaction.Rollback(); } catch { }
                        return (false, "Desde 'Disponible', solo se puede cambiar a 'En Uso' o 'Dañada'.");
                    }
                }
                // --- FIN VALIDACIONES DE FLUJO DE ESTADOS ---


                bobina.IdEstadoBobina = cambiarEstadoDto.NuevoEstadoId;
                bobina.FechaEstado = cambiarEstadoDto.FechaCambioEstado.Date;
                bobina.Obs = cambiarEstadoDto.Obs ?? bobina.Obs;

                if (cambiarEstadoDto.NuevoEstadoId == 2) // 2 = "En Uso"
                {
                    if (!cambiarEstadoDto.IdPublicacion.HasValue || cambiarEstadoDto.IdPublicacion.Value <= 0)
                    {
                        try { transaction.Rollback(); } catch { }
                        return (false, "Para el estado 'En Uso', se requiere Publicación.");
                    }
                    if (!cambiarEstadoDto.IdSeccion.HasValue || cambiarEstadoDto.IdSeccion.Value <= 0)
                    {
                        try { transaction.Rollback(); } catch { }
                        return (false, "Para el estado 'En Uso', se requiere Sección.");
                    }

                    // Validar existencia de Publicación y Sección
                    var publicacion = await _publicacionRepository.GetByIdSimpleAsync(cambiarEstadoDto.IdPublicacion.Value);
                    if (publicacion == null)
                    {
                        try { transaction.Rollback(); } catch { }
                        return (false, "Publicación inválida.");
                    }

                    // Asumiendo que GetByIdAsync en IPubliSeccionRepository valida si la sección pertenece a la publicación también
                    var seccion = await _publiSeccionRepository.GetByIdAsync(cambiarEstadoDto.IdSeccion.Value);
                    if (seccion == null || seccion.IdPublicacion != cambiarEstadoDto.IdPublicacion.Value)
                    {
                        try { transaction.Rollback(); } catch { }
                        return (false, "Sección inválida o no pertenece a la publicación seleccionada.");
                    }

                    bobina.IdPublicacion = cambiarEstadoDto.IdPublicacion.Value;
                    bobina.IdSeccion = cambiarEstadoDto.IdSeccion.Value;
                }
                else // Si el nuevo estado NO es "En Uso" (ej. Disponible, Dañada)
                {
                    bobina.IdPublicacion = null;
                    bobina.IdSeccion = null;
                    // La observación se mantiene o se actualiza según venga en el DTO.
                    // Si se pasa de "Dañada" a "Disponible", el DTO de cambio de estado debe incluir la nueva obs.
                }

                var actualizado = await _stockBobinaRepository.UpdateAsync(bobina, idUsuario, transaction, $"Estado: {nuevoEstado.Denominacion}");
                if (!actualizado) throw new DataException("Error al cambiar estado de la bobina.");

                transaction.Commit();
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback CambiarEstadoBobinaAsync."); }
                _logger.LogError(ex, "Error CambiarEstadoBobinaAsync ID: {IdBobina}", idBobina);
                return (false, $"Error interno: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> EliminarIngresoErroneoAsync(int idBobina, int idUsuario)
        {
            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();
            try
            {
                var bobina = await _stockBobinaRepository.GetByIdAsync(idBobina); // Obtener dentro de la transacción
                if (bobina == null)
                {
                    try { transaction.Rollback(); } catch { }
                    return (false, "Bobina no encontrada.");
                }

                // --- Permitir eliminar si está Disponible (1) o Dañada (3) ---
                if (bobina.IdEstadoBobina != 1 && bobina.IdEstadoBobina != 3)
                {
                    try { transaction.Rollback(); } catch { }
                    return (false, "Solo se pueden eliminar ingresos de bobinas que estén en estado 'Disponible' o 'Dañada'.");
                }

                var eliminado = await _stockBobinaRepository.DeleteAsync(idBobina, idUsuario, transaction);
                if (!eliminado) throw new DataException("Error al eliminar la bobina.");

                transaction.Commit();
                return (true, null);
            }
            catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback EliminarIngresoErroneoAsync."); }
                _logger.LogError(ex, "Error EliminarIngresoErroneoAsync ID: {IdBobina}", idBobina);
                return (false, $"Error interno: {ex.Message}");
            }
        }

        public async Task<IEnumerable<StockBobinaHistorialDto>> ObtenerHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idBobinaAfectada, int? idTipoBobinaFiltro, int? idPlantaFiltro, int? idEstadoBobinaFiltro)
        {
            var historialData = await _stockBobinaRepository.GetHistorialDetalladoAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idBobinaAfectada, idTipoBobinaFiltro, idPlantaFiltro, idEstadoBobinaFiltro);

            return historialData.Select(h => new StockBobinaHistorialDto
            {
                Id_Bobina = h.Historial.Id_Bobina,
                Id_TipoBobina = h.Historial.Id_TipoBobina,
                NombreTipoBobina = h.NombreTipoBobina ?? "N/A",
                NroBobina = h.Historial.NroBobina,
                Peso = h.Historial.Peso,
                Id_Planta = h.Historial.Id_Planta,
                NombrePlanta = h.NombrePlanta ?? "N/A",
                Id_EstadoBobina = h.Historial.Id_EstadoBobina,
                NombreEstadoBobina = h.NombreEstadoBobina ?? "N/A",
                Remito = h.Historial.Remito,
                FechaRemito = h.Historial.FechaRemito,
                FechaEstado = h.Historial.FechaEstado,
                Id_Publicacion = h.Historial.Id_Publicacion,
                NombrePublicacion = h.NombrePublicacion,
                Id_Seccion = h.Historial.Id_Seccion,
                NombreSeccion = h.NombreSeccion,
                Obs = h.Historial.Obs,
                Id_Usuario = h.Historial.Id_Usuario,
                NombreUsuarioModifico = h.NombreUsuarioModifico,
                FechaMod = h.Historial.FechaMod,
                TipoMod = h.Historial.TipoMod
            }).ToList();
        }

        public async Task<IEnumerable<StockBobinaDto>> VerificarRemitoExistenteAsync(int idPlanta, string remito, DateTime? fechaRemito)
        {
            // Si la fecha tiene valor, filtramos por ese día exacto. Si no, busca en cualquier fecha.
            DateTime? fechaDesde = fechaRemito?.Date;
            DateTime? fechaHasta = fechaRemito?.Date;

            var bobinas = await _stockBobinaRepository.GetAllAsync(null, null, idPlanta, null, remito, fechaDesde, fechaHasta, null, null);

            var dtos = new List<StockBobinaDto>();
            foreach (var bobina in bobinas)
            {
                dtos.Add(await MapToDto(bobina));
            }
            return dtos;
        }

        public async Task<(bool Exito, string? Error)> ActualizarFechaRemitoLoteAsync(UpdateFechaRemitoLoteDto dto, int idUsuario)
        {
            // 1. Buscar todas las bobinas que coinciden con el lote a modificar.
            var bobinasAActualizar = await _stockBobinaRepository.GetAllAsync(
                idTipoBobina: null,
                nroBobinaFilter: null,
                idPlanta: dto.IdPlanta,
                idEstadoBobina: null,
                remitoFilter: dto.Remito,
                fechaDesde: dto.FechaRemitoActual.Date,
                fechaHasta: dto.FechaRemitoActual.Date,
                fechaEstadoDesde: null,
                fechaEstadoHasta: null
            );

            if (!bobinasAActualizar.Any())
            {
                return (false, "No se encontraron bobinas para el remito, planta y fecha especificados. Es posible que ya hayan sido modificados.");
            }

            // 2. Iniciar una transacción para asegurar que todas las actualizaciones se completen o ninguna.
            using var connection = _connectionFactory.CreateConnection();
            if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
            using var transaction = connection.BeginTransaction();

            try
            {
                // 3. Iterar sobre cada bobina y actualizarla.
                foreach (var bobina in bobinasAActualizar)
                {
                    // Modificamos solo la fecha del remito.
                    bobina.FechaRemito = dto.NuevaFechaRemito.Date;

                    // Reutilizamos el método UpdateAsync que ya maneja la lógica de historial.
                    // Le pasamos un mensaje específico para el historial.
                    await _stockBobinaRepository.UpdateAsync(bobina, idUsuario, transaction, "Fecha Remito Corregida");
                }

                // 4. Si todo salió bien, confirmar la transacción.
                transaction.Commit();
                _logger.LogInformation(
                    "{Count} bobinas del remito {Remito} (Planta ID {IdPlanta}) actualizadas a nueva fecha {NuevaFecha} por Usuario ID {IdUsuario}.",
                    bobinasAActualizar.Count(), dto.Remito, dto.IdPlanta, dto.NuevaFechaRemito.Date, idUsuario
                );
                return (true, null);
            }
            catch (Exception ex)
            {
                // 5. Si algo falla, revertir todos los cambios.
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error transaccional al actualizar fecha de remito {Remito}.", dto.Remito);
                return (false, $"Error interno al actualizar el lote: {ex.Message}");
            }
        }

        public async Task<(bool Exito, string? Error)> IngresarBobinaLoteAsync(CreateStockBobinaLoteDto loteDto, int idUsuario)
        {
            // --- FASE 1: VALIDACIÓN PREVIA (FUERA DE LA TRANSACCIÓN) ---

            // Validación de la cabecera
            if (await _plantaRepository.GetByIdAsync(loteDto.IdPlanta) == null)
                return (false, "La planta especificada no es válida.");

            // Validación de cada bobina del lote
            foreach (var bobinaDetalle in loteDto.Bobinas)
            {
                if (await _tipoBobinaRepository.GetByIdAsync(bobinaDetalle.IdTipoBobina) == null)
                {
                    return (false, $"El tipo de bobina con ID {bobinaDetalle.IdTipoBobina} no es válido.");
                }

                // Esta es la lectura que causaba el bloqueo. Ahora se hace ANTES de la transacción.
                if (await _stockBobinaRepository.GetByNroBobinaAsync(bobinaDetalle.NroBobina) != null)
                {
                    return (false, $"El número de bobina '{bobinaDetalle.NroBobina}' ya existe en el sistema.");
                }
            }

            // Validación de números de bobina duplicados dentro del mismo lote
            var nrosBobinaEnLote = loteDto.Bobinas.Select(b => b.NroBobina.Trim()).ToList();
            if (nrosBobinaEnLote.Count != nrosBobinaEnLote.Distinct().Count())
            {
                var duplicado = nrosBobinaEnLote.GroupBy(n => n).Where(g => g.Count() > 1).First().Key;
                return (false, $"El número de bobina '{duplicado}' está duplicado en el lote que intenta ingresar.");
            }


            // --- FASE 2: ESCRITURA TRANSACCIONAL ---

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

            try
            {
                // Ahora este bucle solo contiene operaciones de escritura. No habrá bloqueos.
                foreach (var bobinaDetalle in loteDto.Bobinas)
                {
                    var nuevaBobina = new StockBobina
                    {
                        IdTipoBobina = bobinaDetalle.IdTipoBobina,
                        NroBobina = bobinaDetalle.NroBobina,
                        Peso = bobinaDetalle.Peso,
                        IdPlanta = loteDto.IdPlanta,
                        Remito = loteDto.Remito,
                        FechaRemito = loteDto.FechaRemito.Date,
                        IdEstadoBobina = 1, // 1 = Disponible
                        FechaEstado = loteDto.FechaRemito.Date,
                        IdPublicacion = null,
                        IdSeccion = null,
                        Obs = null
                    };

                    var bobinaCreada = await _stockBobinaRepository.CreateAsync(nuevaBobina, idUsuario, transaction);
                    if (bobinaCreada == null)
                    {
                        throw new DataException($"No se pudo crear el registro para la bobina '{nuevaBobina.NroBobina}'.");
                    }
                }

                transaction.Commit();
                _logger.LogInformation("Lote de {Count} bobinas para remito {Remito} ingresado por Usuario ID {UserId}.", loteDto.Bobinas.Count, loteDto.Remito, idUsuario);
                return (true, null);
            }
            catch (Exception ex)
            {
                try { transaction.Rollback(); } catch { }
                _logger.LogError(ex, "Error al ingresar lote de bobinas para remito {Remito}", loteDto.Remito);
                return (false, $"Error interno al procesar el lote: {ex.Message}");
            }
        }
    }
}