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 PorcPagoRepository : IPorcPagoRepository
    {
        private readonly DbConnectionFactory _cf; // Renombrado para brevedad
        private readonly ILogger<PorcPagoRepository> _log; // Renombrado para brevedad

        public PorcPagoRepository(DbConnectionFactory connectionFactory, ILogger<PorcPagoRepository> logger)
        {
            _cf = connectionFactory;
            _log = logger;
        }

        public async Task<IEnumerable<(PorcPago PorcPago, string NombreDistribuidor)>> GetByPublicacionIdAsync(int idPublicacion)
        {
            const string sql = @"
                SELECT 
                    pp.Id_Porcentaje AS IdPorcentaje, pp.Id_Publicacion AS IdPublicacion, pp.Id_Distribuidor AS IdDistribuidor,
                    pp.VigenciaD, pp.VigenciaH, pp.Porcentaje,
                    d.Nombre AS NombreDistribuidor
                FROM dbo.dist_PorcPago pp
                INNER JOIN dbo.dist_dtDistribuidores d ON pp.Id_Distribuidor = d.Id_Distribuidor
                WHERE pp.Id_Publicacion = @IdPublicacionParam AND d.Baja = 0
                ORDER BY d.Nombre, pp.VigenciaD DESC";
            try
            {
                using var connection = _cf.CreateConnection();
                return await connection.QueryAsync<PorcPago, string, (PorcPago, string)>(
                    sql,
                    (porcPago, nombreDist) => (porcPago, nombreDist),
                    new { IdPublicacionParam = idPublicacion },
                    splitOn: "NombreDistribuidor"
                );
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error al obtener Porcentajes de Pago por Publicacion ID: {IdPublicacion}", idPublicacion);
                return Enumerable.Empty<(PorcPago, string)>();
            }
        }

        public async Task<PorcPago?> GetByIdAsync(int idPorcentaje)
        {
            const string sql = @"
                SELECT 
                    Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
                    VigenciaD, VigenciaH, Porcentaje
                FROM dbo.dist_PorcPago 
                WHERE Id_Porcentaje = @IdPorcentajeParam";
            try
            {
                using var connection = _cf.CreateConnection();
                return await connection.QuerySingleOrDefaultAsync<PorcPago>(sql, new { IdPorcentajeParam = idPorcentaje });
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error al obtener Porcentaje de Pago por ID: {IdPorcentaje}", idPorcentaje);
                return null;
            }
        }

        public async Task<PorcPago?> GetActiveByPublicacionDistribuidorAndDateAsync(int idPublicacion, int idDistribuidor, DateTime fecha, IDbTransaction? transaction = null)
        {
            const string sql = @"
                SELECT TOP 1 
                    Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
                    VigenciaD, VigenciaH, Porcentaje
                FROM dbo.dist_PorcPago
                WHERE Id_Publicacion = @IdPublicacionParam AND Id_Distribuidor = @IdDistribuidorParam
                  AND VigenciaD <= @FechaParam
                  AND (VigenciaH IS NULL OR VigenciaH >= @FechaParam)
                ORDER BY VigenciaD DESC;";

            var cn = transaction?.Connection ?? _cf.CreateConnection();
            bool ownConnection = transaction == null;
            PorcPago? result = null;
            try
            {
                if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
                result = await cn.QuerySingleOrDefaultAsync<PorcPago>(sql, new { IdPublicacionParam = idPublicacion, IdDistribuidorParam = idDistribuidor, FechaParam = fecha.Date }, transaction);
            }
            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<PorcPago?> GetPreviousActivePorcPagoAsync(int idPublicacion, int idDistribuidor, DateTime vigenciaDNuevo, IDbTransaction transaction)
        {
            const string sql = @"
                SELECT TOP 1 
                    Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
                    VigenciaD, VigenciaH, Porcentaje
                FROM dbo.dist_PorcPago
                WHERE Id_Publicacion = @IdPublicacionParam AND Id_Distribuidor = @IdDistribuidorParam
                  AND VigenciaD < @VigenciaDNuevoParam
                  AND VigenciaH IS NULL
                ORDER BY VigenciaD DESC;";
            return await transaction.Connection!.QuerySingleOrDefaultAsync<PorcPago>(sql,
                new { IdPublicacionParam = idPublicacion, IdDistribuidorParam = idDistribuidor, VigenciaDNuevoParam = vigenciaDNuevo.Date },
                transaction);
        }

        public async Task<PorcPago?> CreateAsync(PorcPago nuevoPorcPago, int idUsuario, IDbTransaction transaction)
        {
            const string sqlInsert = @"
                INSERT INTO dbo.dist_PorcPago (Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje)
                OUTPUT INSERTED.Id_Porcentaje AS IdPorcentaje, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Distribuidor AS IdDistribuidor, 
                       INSERTED.VigenciaD, INSERTED.VigenciaH, INSERTED.Porcentaje
                VALUES (@IdPublicacion, @IdDistribuidor, @VigenciaD, @VigenciaH, @Porcentaje);";
            const string sqlInsertHistorico = @"
                INSERT INTO dbo.dist_PorcPago_H (Id_Porcentaje, Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdPorcentajeHist, @IdPublicacionHist, @IdDistribuidorHist, @VigenciaDHist, @VigenciaHHist, @PorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";

            var inserted = await transaction.Connection!.QuerySingleAsync<PorcPago>(sqlInsert, nuevoPorcPago, transaction);
            if (inserted == null || inserted.IdPorcentaje == 0) throw new DataException("Error al crear porcentaje de pago o al obtener ID.");

            await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
            {
                IdPorcentajeHist = inserted.IdPorcentaje,
                IdPublicacionHist = inserted.IdPublicacion,
                IdDistribuidorHist = inserted.IdDistribuidor,
                VigenciaDHist = inserted.VigenciaD,
                VigenciaHHist = inserted.VigenciaH,
                PorcentajeHist = inserted.Porcentaje,
                IdUsuarioHist = idUsuario,
                FechaModHist = DateTime.Now,
                TipoModHist = "Creado"
            }, transaction);
            return inserted;
        }

        public async Task<bool> UpdateAsync(PorcPago porcPagoAActualizar, int idUsuario, IDbTransaction transaction)
        {
            var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PorcPago>(
                @"SELECT Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor, 
                         VigenciaD, VigenciaH, Porcentaje 
                  FROM dbo.dist_PorcPago WHERE Id_Porcentaje = @IdPorcentajeParam",
                new { IdPorcentajeParam = porcPagoAActualizar.IdPorcentaje }, transaction);
            if (actual == null) throw new KeyNotFoundException("Porcentaje de pago no encontrado.");

            const string sqlUpdate = @"
                UPDATE dbo.dist_PorcPago SET
                    Porcentaje = @Porcentaje, VigenciaH = @VigenciaH
                WHERE Id_Porcentaje = @IdPorcentaje;";
            const string sqlInsertHistorico = @"
                INSERT INTO dbo.dist_PorcPago_H (Id_Porcentaje, Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdPorcentajeHist, @IdPublicacionHist, @IdDistribuidorHist, @VigenciaDHist, @VigenciaHHist, @PorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";

            await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
            {
                IdPorcentajeHist = actual.IdPorcentaje,
                IdPublicacionHist = actual.IdPublicacion,
                IdDistribuidorHist = actual.IdDistribuidor,
                VigenciaDHist = actual.VigenciaD,
                VigenciaHHist = actual.VigenciaH, // Valor ANTERIOR de VigenciaH
                PorcentajeHist = actual.Porcentaje, // Valor ANTERIOR de Porcentaje
                IdUsuarioHist = idUsuario,
                FechaModHist = DateTime.Now,
                TipoModHist = "Actualizado"
            }, transaction);

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

        public async Task<bool> DeleteAsync(int idPorcentaje, int idUsuario, IDbTransaction transaction)
        {
            var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PorcPago>(
               @"SELECT Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor, 
                        VigenciaD, VigenciaH, Porcentaje 
                 FROM dbo.dist_PorcPago WHERE Id_Porcentaje = @IdPorcentajeParam",
               new { IdPorcentajeParam = idPorcentaje }, transaction);
            if (actual == null) throw new KeyNotFoundException("Porcentaje de pago no encontrado para eliminar.");

            const string sqlDelete = "DELETE FROM dbo.dist_PorcPago WHERE Id_Porcentaje = @IdPorcentajeParam";
            const string sqlInsertHistorico = @"
                INSERT INTO dbo.dist_PorcPago_H (Id_Porcentaje, Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje, Id_Usuario, FechaMod, TipoMod)
                VALUES (@IdPorcentajeHist, @IdPublicacionHist, @IdDistribuidorHist, @VigenciaDHist, @VigenciaHHist, @PorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";

            await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
            {
                IdPorcentajeHist = actual.IdPorcentaje,
                IdPublicacionHist = actual.IdPublicacion,
                IdDistribuidorHist = actual.IdDistribuidor,
                VigenciaDHist = actual.VigenciaD,
                VigenciaHHist = actual.VigenciaH,
                PorcentajeHist = actual.Porcentaje,
                IdUsuarioHist = idUsuario,
                FechaModHist = DateTime.Now,
                TipoModHist = "Eliminado"
            }, transaction);

            var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPorcentajeParam = idPorcentaje }, transaction);
            return rowsAffected == 1;
        }

        public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
        {
            const string selectSql = @"
                SELECT Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor, 
                       VigenciaD, VigenciaH, Porcentaje 
                FROM dbo.dist_PorcPago WHERE Id_Publicacion = @IdPublicacionParam";
            var itemsToDelete = await transaction.Connection!.QueryAsync<PorcPago>(selectSql, new { IdPublicacionParam = idPublicacion }, transaction);

            if (itemsToDelete.Any())
            {
                const string insertHistoricoSql = @"
                    INSERT INTO dbo.dist_PorcPago_H (Id_Porcentaje, Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje, Id_Usuario, FechaMod, TipoMod)
                    VALUES (@IdPorcentajeHist, @IdPublicacionHist, @IdDistribuidorHist, @VigenciaDHist, @VigenciaHHist, @PorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
                foreach (var item in itemsToDelete)
                {
                    await transaction.Connection!.ExecuteAsync(insertHistoricoSql, new
                    {
                        IdPorcentajeHist = item.IdPorcentaje,
                        IdPublicacionHist = item.IdPublicacion,
                        IdDistribuidorHist = item.IdDistribuidor,
                        VigenciaDHist = item.VigenciaD,
                        VigenciaHHist = item.VigenciaH,
                        PorcentajeHist = item.Porcentaje,
                        IdUsuarioHist = idUsuarioAuditoria,
                        FechaModHist = DateTime.Now,
                        TipoModHist = "Eliminado (Cascada)"
                    }, transaction);
                }
            }
            const string deleteSql = "DELETE FROM dbo.dist_PorcPago WHERE Id_Publicacion = @IdPublicacionParam";
            try
            {
                await transaction.Connection!.ExecuteAsync(deleteSql, new { IdPublicacionParam = idPublicacion }, transaction: transaction);
                return true;
            }
            catch (System.Exception ex)
            {
                _log.LogError(ex, "Error al eliminar PorcPago por IdPublicacion: {idPublicacion}", idPublicacion);
                throw;
            }
        }

        public async Task<IEnumerable<(PorcPagoHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
        DateTime? fechaDesde, DateTime? fechaHasta,
        int? idUsuarioModifico, string? tipoModificacion,
        int? idPorcentajeOriginal, int? idPublicacionOriginal, int? idDistribuidorOriginal)
        {
            using var connection = _cf.CreateConnection();
            var sqlBuilder = new StringBuilder(@"
            SELECT 
                h.Id_Porcentaje, h.Id_Publicacion, h.Id_Distribuidor, 
                h.VigenciaD, h.VigenciaH, h.Porcentaje,
                h.Id_Usuario, h.FechaMod, h.TipoMod,
                u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
            FROM dbo.dist_PorcPago_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 (idPorcentajeOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Porcentaje = @IdPorcentajeOriginalParam"); parameters.Add("IdPorcentajeOriginalParam", idPorcentajeOriginal.Value); }
            if (idPublicacionOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Publicacion = @IdPublicacionOriginalParam"); parameters.Add("IdPublicacionOriginalParam", idPublicacionOriginal.Value); }
            if (idDistribuidorOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Distribuidor = @IdDistribuidorOriginalParam"); parameters.Add("IdDistribuidorOriginalParam", idDistribuidorOriginal.Value); }

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

            try
            {
                var result = await connection.QueryAsync<PorcPagoHistorico, string, (PorcPagoHistorico, string)>(
                    sqlBuilder.ToString(),
                    (hist, userName) => (hist, userName),
                    parameters,
                    splitOn: "NombreUsuarioModifico"
                );
                return result;
            }
            catch (Exception ex)
            {
                _log.LogError(ex, "Error al obtener historial de Porcentajes de Pago (Distribuidores).");
                return Enumerable.Empty<(PorcPagoHistorico, string)>();
            }
        }
    }
}