using Dapper;
using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GestionIntegral.Api.Data.Repositories.Distribucion
{
    public class PrecioRepository : IPrecioRepository
    {
        private readonly DbConnectionFactory _connectionFactory;
        private readonly ILogger<PrecioRepository> _logger;

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

        public async Task<IEnumerable<Precio>> GetByPublicacionIdAsync(int idPublicacion)
        {
            const string sql = @"
                SELECT 
                    Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH, 
                    Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
                FROM dbo.dist_Precios 
                WHERE Id_Publicacion = @IdPublicacionParam 
                ORDER BY VigenciaD DESC";
            try
            {
                using var connection = _connectionFactory.CreateConnection();
                return await connection.QueryAsync<Precio>(sql, new { IdPublicacionParam = idPublicacion });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener precios por IdPublicacion: {IdPublicacion}", idPublicacion);
                return Enumerable.Empty<Precio>();
            }
        }

        public async Task<Precio?> GetByIdAsync(int idPrecio)
        {
            const string sql = @"
                SELECT 
                    Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH, 
                    Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
                FROM dbo.dist_Precios 
                WHERE Id_Precio = @IdPrecioParam";
            try
            {
                using var connection = _connectionFactory.CreateConnection();
                return await connection.QuerySingleOrDefaultAsync<Precio>(sql, new { IdPrecioParam = idPrecio });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error al obtener precio por ID: {idPrecio}", idPrecio);
                return null;
            }
        }

        public async Task<Precio?> GetActiveByPublicacionAndDateAsync(int idPublicacion, DateTime fecha, IDbTransaction? transaction = null)
        {
            const string sql = @"
                SELECT TOP 1 
                    Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH, 
                    Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
                FROM dbo.dist_Precios
                WHERE Id_Publicacion = @IdPublicacionParam AND VigenciaD <= @FechaParam
                  AND (VigenciaH IS NULL OR VigenciaH >= @FechaParam)
                ORDER BY VigenciaD DESC;";

            var cn = transaction?.Connection ?? _connectionFactory.CreateConnection();
            bool ownConnection = transaction == null;
            Precio? result = null;
            try
            {
                if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn)
                {
                    await dbConn.OpenAsync();
                }
                result = await cn.QuerySingleOrDefaultAsync<Precio>(sql,
                    new { IdPublicacionParam = idPublicacion, FechaParam = fecha.Date },
                    transaction);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error en GetActiveByPublicacionAndDateAsync. IdPublicacion: {IdPublicacion}, Fecha: {Fecha}", idPublicacion, fecha);
                throw; // Re-lanzar para que el servicio lo maneje si es necesario
            }
            finally
            {
                if (ownConnection && cn.State == ConnectionState.Open && cn is System.Data.Common.DbConnection dbConnClose)
                {
                    await dbConnClose.CloseAsync();
                }
                if (ownConnection) (cn as IDisposable)?.Dispose();
            }
            return result;
        }

        public async Task<Precio?> GetPreviousActivePriceAsync(int idPublicacion, DateTime vigenciaDNuevo, IDbTransaction transaction)
        {
            const string sql = @"
                SELECT TOP 1 
                    Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH, 
                    Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
                FROM dbo.dist_Precios
                WHERE Id_Publicacion = @IdPublicacionParam
                  AND VigenciaD < @VigenciaDNuevoParam
                  AND VigenciaH IS NULL
                ORDER BY VigenciaD DESC;";
            return await transaction.Connection!.QuerySingleOrDefaultAsync<Precio>(sql,
                new { IdPublicacionParam = idPublicacion, VigenciaDNuevoParam = vigenciaDNuevo.Date },
                transaction);
        }

        public async Task<Precio?> CreateAsync(Precio nuevoPrecio, int idUsuario, IDbTransaction transaction)
        {
            const string sqlInsert = @"
                INSERT INTO dbo.dist_Precios (Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo)
                OUTPUT INSERTED.Id_Precio AS IdPrecio, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.VigenciaD, INSERTED.VigenciaH, 
                       INSERTED.Lunes, INSERTED.Martes, INSERTED.Miercoles, INSERTED.Jueves, INSERTED.Viernes, INSERTED.Sabado, INSERTED.Domingo
                VALUES (@IdPublicacion, @VigenciaD, @VigenciaH, @Lunes, @Martes, @Miercoles, @Jueves, @Viernes, @Sabado, @Domingo);";
            var inserted = await transaction.Connection!.QuerySingleAsync<Precio>(sqlInsert, nuevoPrecio, transaction);
            if (inserted == null || inserted.IdPrecio == 0) throw new DataException("Error al crear precio o al obtener el ID generado.");

            const string sqlInsertHistorico = @"
                INSERT INTO dbo.dist_Precios_H
                    (Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdPrecioHist, @IdPublicacionHist, @VigenciaDHist, @VigenciaHHist, @LunesHist, @MartesHist, @MiercolesHist, @JuevesHist, @ViernesHist, @SabadoHist, @DomingoHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";

            await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
            {
                IdPrecioHist = inserted.IdPrecio,
                IdPublicacionHist = inserted.IdPublicacion,
                VigenciaDHist = inserted.VigenciaD,
                VigenciaHHist = inserted.VigenciaH,
                LunesHist = inserted.Lunes,
                MartesHist = inserted.Martes,
                MiercolesHist = inserted.Miercoles,
                JuevesHist = inserted.Jueves,
                ViernesHist = inserted.Viernes,
                SabadoHist = inserted.Sabado,
                DomingoHist = inserted.Domingo,
                IdUsuarioHist = idUsuario,
                FechaModHist = DateTime.Now,
                TipoModHist = "Creado"
            }, transaction);
            return inserted;
        }

        public async Task<bool> UpdateAsync(Precio precioAActualizar, int idUsuario, IDbTransaction transaction)
        {
            var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<Precio>(
                @"SELECT Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH, 
                         Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
                  FROM dbo.dist_Precios WHERE Id_Precio = @IdPrecioParam", // Usar parámetro con alias
                new { IdPrecioParam = precioAActualizar.IdPrecio }, transaction);
            if (actual == null) throw new KeyNotFoundException("Precio no encontrado.");
            const string sqlUpdate = @"
                UPDATE dbo.dist_Precios SET
                    VigenciaH = @VigenciaH, Lunes = @Lunes, Martes = @Martes, Miercoles = @Miercoles,
                    Jueves = @Jueves, Viernes = @Viernes, Sabado = @Sabado, Domingo = @Domingo
                WHERE Id_Precio = @IdPrecio;";
            const string sqlInsertHistorico = @"
                INSERT INTO dbo.dist_Precios_H
                    (Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdPrecioHist, @IdPublicacionHist, @VigenciaDHist, @VigenciaHHist, @LunesHist, @MartesHist, @MiercolesHist, @JuevesHist, @ViernesHist, @SabadoHist, @DomingoHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";

            await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
            {
                IdPrecioHist = actual.IdPrecio,
                IdPublicacionHist = actual.IdPublicacion,
                VigenciaDHist = actual.VigenciaD,
                VigenciaHHist = actual.VigenciaH,
                LunesHist = actual.Lunes,
                MartesHist = actual.Martes,
                MiercolesHist = actual.Miercoles,
                JuevesHist = actual.Jueves,
                ViernesHist = actual.Viernes,
                SabadoHist = actual.Sabado,
                DomingoHist = actual.Domingo,
                IdUsuarioHist = idUsuario,
                FechaModHist = DateTime.Now,
                TipoModHist = "Actualizado"
            }, transaction);

            var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, precioAActualizar, transaction);
            return rowsAffected == 1;
        }

        public async Task<bool> DeleteAsync(int idPrecio, int idUsuario, IDbTransaction transaction)
        {
            var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<Precio>(
               @"SELECT Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH, 
                         Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
                  FROM dbo.dist_Precios WHERE Id_Precio = @IdPrecioParam", new { IdPrecioParam = idPrecio }, transaction);
            if (actual == null) throw new KeyNotFoundException("Precio no encontrado para eliminar.");

            const string sqlDelete = "DELETE FROM dbo.dist_Precios WHERE Id_Precio = @IdPrecioParam"; // Usar parámetro con alias
            const string sqlInsertHistorico = @"
                 INSERT INTO dbo.dist_Precios_H
                    (Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdPrecioHist, @IdPublicacionHist, @VigenciaDHist, @VigenciaHHist, @LunesHist, @MartesHist, @MiercolesHist, @JuevesHist, @ViernesHist, @SabadoHist, @DomingoHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";

            await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
            {
                IdPrecioHist = actual.IdPrecio,
                IdPublicacionHist = actual.IdPublicacion,
                VigenciaDHist = actual.VigenciaD,
                VigenciaHHist = actual.VigenciaH,
                LunesHist = actual.Lunes,
                MartesHist = actual.Martes,
                MiercolesHist = actual.Miercoles,
                JuevesHist = actual.Jueves,
                ViernesHist = actual.Viernes,
                SabadoHist = actual.Sabado,
                DomingoHist = actual.Domingo,
                IdUsuarioHist = idUsuario,
                FechaModHist = DateTime.Now,
                TipoModHist = "Eliminado"
            }, transaction);

            var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPrecioParam = idPrecio }, transaction); // Usar parámetro con alias
            return rowsAffected == 1;
        }

        public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
        {
            const string selectSql = @"
                SELECT 
                    Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH, 
                    Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
                FROM dbo.dist_Precios WHERE Id_Publicacion = @IdPublicacionParam";
            var itemsToDelete = await transaction.Connection!.QueryAsync<Precio>(selectSql, new { IdPublicacionParam = idPublicacion }, transaction);

            if (itemsToDelete.Any())
            {
                const string insertHistoricoSql = @"
                    INSERT INTO dbo.dist_Precios_H
                        (Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
                    VALUES (@IdPrecioHist, @IdPublicacionHist, @VigenciaDHist, @VigenciaHHist, @LunesHist, @MartesHist, @MiercolesHist, @JuevesHist, @ViernesHist, @SabadoHist, @DomingoHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";

                foreach (var item in itemsToDelete)
                {
                    await transaction.Connection!.ExecuteAsync(insertHistoricoSql, new
                    {
                        IdPrecioHist = item.IdPrecio,
                        IdPublicacionHist = item.IdPublicacion,
                        VigenciaDHist = item.VigenciaD,
                        VigenciaHHist = item.VigenciaH,
                        LunesHist = item.Lunes,
                        MartesHist = item.Martes,
                        MiercolesHist = item.Miercoles,
                        JuevesHist = item.Jueves,
                        ViernesHist = item.Viernes,
                        SabadoHist = item.Sabado,
                        DomingoHist = item.Domingo,
                        IdUsuarioHist = idUsuarioAuditoria,
                        FechaModHist = DateTime.Now,
                        TipoModHist = "Eliminado (Cascada)"
                    }, transaction);
                }
            }
            const string deleteSql = "DELETE FROM dbo.dist_Precios WHERE Id_Publicacion = @IdPublicacionParam";
            try
            {
                await transaction.Connection!.ExecuteAsync(deleteSql, new { IdPublicacionParam = idPublicacion }, transaction: transaction);
                return true;
            }
            catch (System.Exception ex)
            {
                _logger.LogError(ex, "Error al eliminar precios por IdPublicacion: {IdPublicacion}", idPublicacion);
                throw;
            }
        }

        public async Task<bool> IsInUseAsync(int idPrecio, DateTime vigenciaD, DateTime? vigenciaH)
        {
            // Verificar si el Id_Precio se usa en movimientos DENTRO del rango de vigencia del precio
            // Si VigenciaH es NULL, significa que el precio está activo indefinidamente hacia el futuro.
            var sql = @"
                SELECT TOP 1 1 
                FROM (
                    SELECT Id_Precio, Fecha FROM dbo.dist_EntradasSalidas
                    UNION ALL
                    SELECT Id_Precio, Fecha FROM dbo.dist_EntradasSalidasCanillas
                ) AS Movimientos
                WHERE Movimientos.Id_Precio = @IdPrecioParam 
                AND Movimientos.Fecha >= @VigenciaDParam
                AND (@VigenciaHParam IS NULL OR Movimientos.Fecha <= @VigenciaHParam);";
            try
            {
                using var connection = _connectionFactory.CreateConnection();
                var parameters = new
                {
                    IdPrecioParam = idPrecio,
                    VigenciaDParam = vigenciaD.Date,
                    VigenciaHParam = vigenciaH?.Date
                };
                var inUse = await connection.ExecuteScalarAsync<int?>(sql, parameters);
                return inUse.HasValue && inUse.Value == 1;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error en IsInUseAsync para Precio ID: {IdPrecio}", idPrecio);
                return true; // Asumir en uso si hay error, para ser cauteloso
            }
        }

        public async Task<IEnumerable<(PrecioHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idPrecioOriginal, int? idPublicacionOriginal)
        {
            using var connection = _connectionFactory.CreateConnection();
            var sqlBuilder = new StringBuilder(@"
            SELECT 
                h.Id_Precio, h.Id_Publicacion, h.VigenciaD, h.VigenciaH, 
                h.Lunes, h.Martes, h.Miercoles, h.Jueves, h.Viernes, h.Sabado, h.Domingo,
                h.Id_Usuario, h.FechaMod, h.TipoMod,
                u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
            FROM dbo.dist_Precios_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 (idPrecioOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Precio = @IdPrecioOriginalParam"); parameters.Add("IdPrecioOriginalParam", idPrecioOriginal.Value); }
            if (idPublicacionOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Publicacion = @IdPublicacionOriginalParam"); parameters.Add("IdPublicacionOriginalParam", idPublicacionOriginal.Value); }

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

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