using Dapper;
using GestionIntegral.Api.Models.Impresion;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Data;
using System.Text; // Para StringBuilder
using System.Threading.Tasks;

namespace GestionIntegral.Api.Data.Repositories.Impresion
{
    public class PlantaRepository : IPlantaRepository
    {
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<PlantaRepository> _logger;

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

        public async Task<IEnumerable<Planta>> GetAllAsync(string? nombreFilter, string? detalleFilter)
        {
            var sqlBuilder = new StringBuilder("SELECT Id_Planta AS IdPlanta, Nombre, Detalle FROM dbo.bob_dtPlantas WHERE 1=1");
            var parameters = new DynamicParameters();

            if (!string.IsNullOrWhiteSpace(nombreFilter))
            {
                sqlBuilder.Append(" AND Nombre LIKE @NombreFilter");
                parameters.Add("NombreFilter", $"%{nombreFilter}%");
            }
            if (!string.IsNullOrWhiteSpace(detalleFilter))
            {
                sqlBuilder.Append(" AND Detalle LIKE @DetalleFilter");
                parameters.Add("DetalleFilter", $"%{detalleFilter}%");
            }
            sqlBuilder.Append(" ORDER BY Nombre;");

            try
            {
                using var connection = _connectionFactory.CreateConnection();
                return await connection.QueryAsync<Planta>(sqlBuilder.ToString(), parameters);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener todas las Plantas. Filtros: Nombre={Nombre}, Detalle={Detalle}", nombreFilter, detalleFilter);
                return Enumerable.Empty<Planta>();
            }
        }

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

        public async Task<bool> ExistsByNameAsync(string nombre, int? excludeId = null)
        {
            var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.bob_dtPlantas WHERE Nombre = @Nombre");
            var parameters = new DynamicParameters();
            parameters.Add("Nombre", nombre);

            if (excludeId.HasValue)
            {
                sqlBuilder.Append(" AND Id_Planta != @ExcludeId");
                parameters.Add("ExcludeId", excludeId.Value);
            }

            try
            {
                using var connection = _connectionFactory.CreateConnection();
                var count = await connection.ExecuteScalarAsync<int>(sqlBuilder.ToString(), parameters);
                return count > 0;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error en ExistsByNameAsync para Planta con nombre: {Nombre}", nombre);
                // Asumir que existe en caso de error para prevenir duplicados accidentales
                return true;
            }
        }

        public async Task<bool> IsInUseAsync(int id)
        {
            // Verificar si la planta está referenciada en bob_StockBobinas O bob_RegPublicaciones
            const string sqlCheckStock = "SELECT TOP 1 1 FROM dbo.bob_StockBobinas WHERE Id_Planta = @IdPlanta";
            const string sqlCheckRegPubli = "SELECT TOP 1 1 FROM dbo.bob_RegPublicaciones WHERE Id_Planta = @IdPlanta";

            try
            {
                using var connection = _connectionFactory.CreateConnection();
                var inStock = await connection.ExecuteScalarAsync<int?>(sqlCheckStock, new { IdPlanta = id });
                if (inStock.HasValue && inStock.Value == 1) return true;

                var inRegPubli = await connection.ExecuteScalarAsync<int?>(sqlCheckRegPubli, new { IdPlanta = id });
                return inRegPubli.HasValue && inRegPubli.Value == 1;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error en IsInUseAsync para Planta ID: {IdPlanta}", id);
                // Asumir que está en uso si hay error para prevenir borrado incorrecto
                return true;
            }
        }

        // --- Métodos de Escritura (USAN TRANSACCIÓN PASADA DESDE EL SERVICIO) ---

        public async Task<Planta?> CreateAsync(Planta nuevaPlanta, int idUsuario, IDbTransaction transaction)
        {
            const string sqlInsert = @"
                INSERT INTO dbo.bob_dtPlantas (Nombre, Detalle)
                OUTPUT INSERTED.Id_Planta AS IdPlanta, INSERTED.Nombre, INSERTED.Detalle
                VALUES (@Nombre, @Detalle);";
            const string sqlInsertHistorico = @"
                INSERT INTO dbo.bob_dtPlantas_H (Id_Planta, Nombre, Detalle, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdPlanta, @Nombre, @Detalle, @IdUsuario, @FechaMod, @TipoMod);";

            // Dapper requiere que la conexión esté asociada a la transacción
            var connection = transaction.Connection ?? throw new InvalidOperationException("Transaction has no associated connection.");

            var insertedPlanta = await connection.QuerySingleAsync<Planta>(
                sqlInsert,
                new { nuevaPlanta.Nombre, nuevaPlanta.Detalle },
                transaction: transaction // Pasar la transacción
            );

            if (insertedPlanta == null || insertedPlanta.IdPlanta <= 0)
            {
                throw new DataException("No se pudo obtener el ID de la planta insertada."); // Usar DataException
            }

            // Insertar en historial
            await connection.ExecuteAsync(sqlInsertHistorico, new
            {
                IdPlanta = insertedPlanta.IdPlanta,
                insertedPlanta.Nombre,
                insertedPlanta.Detalle,
                IdUsuario = idUsuario,
                FechaMod = DateTime.Now,
                TipoMod = "Insertada"
            }, transaction: transaction);

            return insertedPlanta; // Devolver la entidad con el ID
        }

        public async Task<bool> UpdateAsync(Planta plantaAActualizar, int idUsuario, IDbTransaction transaction)
        {
            // El servicio ya verificó que existe. Obtener estado actual para historial dentro de la transacción.
            var connection = transaction.Connection!;
            var plantaActual = await connection.QuerySingleOrDefaultAsync<Planta>(
                "SELECT Id_Planta AS IdPlanta, Nombre, Detalle FROM dbo.bob_dtPlantas WHERE Id_Planta = @Id",
                 new { Id = plantaAActualizar.IdPlanta },
                 transaction);

            if (plantaActual == null) throw new KeyNotFoundException($"No se encontró la planta con ID {plantaAActualizar.IdPlanta} para actualizar."); // Más específico

            const string sqlUpdate = @"
                UPDATE dbo.bob_dtPlantas
                SET Nombre = @Nombre, Detalle = @Detalle
                WHERE Id_Planta = @IdPlanta;";
            const string sqlInsertHistorico = @"
                INSERT INTO dbo.bob_dtPlantas_H (Id_Planta, Nombre, Detalle, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdPlanta, @NombreActual, @DetalleActual, @IdUsuario, @FechaMod, @TipoMod);";

            // Insertar en historial (estado anterior)
            await connection.ExecuteAsync(sqlInsertHistorico, new
            {
                IdPlanta = plantaActual.IdPlanta,
                NombreActual = plantaActual.Nombre,
                DetalleActual = plantaActual.Detalle,
                IdUsuario = idUsuario,
                FechaMod = DateTime.Now,
                TipoMod = "Modificada"
            }, transaction: transaction);

            // Actualizar principal
            var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new
            {
                plantaAActualizar.Nombre,
                plantaAActualizar.Detalle,
                plantaAActualizar.IdPlanta
            }, transaction: transaction);

            return rowsAffected == 1;
        }

        public async Task<bool> DeleteAsync(int id, int idUsuario, IDbTransaction transaction)
        {
            var connection = transaction.Connection!;
            // Obtener datos para historial ANTES de borrar
            var plantaActual = await connection.QuerySingleOrDefaultAsync<Planta>(
                 "SELECT Id_Planta AS IdPlanta, Nombre, Detalle FROM dbo.bob_dtPlantas WHERE Id_Planta = @Id",
                 new { Id = id },
                 transaction);

            if (plantaActual == null) throw new KeyNotFoundException($"No se encontró la planta con ID {id} para eliminar.");

            const string sqlDelete = "DELETE FROM dbo.bob_dtPlantas WHERE Id_Planta = @Id";
            const string sqlInsertHistorico = @"
                INSERT INTO dbo.bob_dtPlantas_H (Id_Planta, Nombre, Detalle, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdPlanta, @Nombre, @Detalle, @IdUsuario, @FechaMod, @TipoMod);";

            // Insertar en historial (estado antes de borrar)
            await connection.ExecuteAsync(sqlInsertHistorico, new
            {
                IdPlanta = plantaActual.IdPlanta,
                plantaActual.Nombre,
                plantaActual.Detalle,
                IdUsuario = idUsuario,
                FechaMod = DateTime.Now,
                TipoMod = "Eliminada"
            }, transaction: transaction);

            // Eliminar de la tabla principal
            var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { Id = id }, transaction: transaction);

            return rowsAffected == 1;
        }

        public async Task<IEnumerable<(PlantaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idPlantaOriginal)
        {
            using var connection = _connectionFactory.CreateConnection();
            var sqlBuilder = new StringBuilder(@"
            SELECT 
                h.Id_Planta, h.Nombre, h.Detalle,
                h.Id_Usuario, h.FechaMod, h.TipoMod,
                u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
            FROM dbo.bob_dtPlantas_H h
            JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.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 (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
            if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
            if (idPlantaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Planta = @IdPlantaOriginalParam"); parameters.Add("IdPlantaOriginalParam", idPlantaOriginal.Value); }

            sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");

            try
            {
                var result = await connection.QueryAsync<PlantaHistorico, string, (PlantaHistorico, string)>(
                    sqlBuilder.ToString(),
                    (hist, userName) => (hist, userName),
                    parameters,
                    splitOn: "NombreUsuarioModifico"
                );
                return result;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener historial de Plantas de Impresión.");
                return Enumerable.Empty<(PlantaHistorico, string)>();
            }
        }
    }
}